Integrate create new graph with extension and react webview, and also use OS credentials to store connection string. (#24)

* Integrate create new graph with extension and react webview, and also use OS credentials to store connection string.

* move vscode away from global context.

* fix audit issues.
This commit is contained in:
giakas 2020-08-11 12:45:45 -07:00 коммит произвёл GitHub
Родитель bd0f208916
Коммит 5041915930
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 14544 добавлений и 14315 удалений

Просмотреть файл

@ -9,4 +9,11 @@ export class GraphTopologyData {
});
return response?.value;
}
public static putGraphTopology(iotHubData: IotHubData, deviceId: string, moduleId: string, graphData: any): Promise<MediaGraphTopology[]> {
return iotHubData.directMethodCall(deviceId, moduleId, "GraphTopologySet", {
"@apiVersion": Constants.ApiVersion.version1,
...graphData
});
}
}

Просмотреть файл

@ -0,0 +1,70 @@
import * as vscode from "vscode";
import { GraphTopologyData } from "../Data/GraphTolologyData";
import { IotHubData } from "../Data/IotHubData";
import { MediaGraphInstance, MediaGraphTopology } from "../lva-sdk/lvaSDKtypes";
import { Constants } from "../Util/Constants";
import Localizer from "../Util/Localizer";
import { GraphEditorPanel } from "../Webview/GraphPanel";
import { InstanceItem } from "./InstanceItem";
import { INode } from "./Node";
export class GraphTopologyItem extends vscode.TreeItem {
constructor(
public iotHubData: IotHubData,
public readonly deviceId: string,
public readonly moduleId: string,
public readonly graphTopology?: MediaGraphTopology,
private readonly _graphInstances?: MediaGraphInstance[]
) {
super(graphTopology?.name ?? Localizer.localize("createGraphButton"), vscode.TreeItemCollapsibleState.Expanded);
if (graphTopology) {
this.iconPath = new vscode.ThemeIcon("primitive-square");
} else {
this.iconPath = new vscode.ThemeIcon("add");
this.command = { title: Localizer.localize("createGraphButton"), command: "lvaTopologyEditor.start", arguments: [this] };
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
}
}
public getChildren(): Promise<INode[]> | INode[] {
if (this.graphTopology == null || this._graphInstances == null) {
return [];
}
return (
this._graphInstances
?.filter((instance) => {
return instance?.properties?.topologyName === this.graphTopology?.name;
})
?.map((instance) => {
return new InstanceItem(instance);
}) ?? []
);
}
public createNewGraphCommand(context: vscode.ExtensionContext) {
const createGraphPanel = GraphEditorPanel.createOrShow(context.extensionPath);
if (createGraphPanel) {
createGraphPanel.registerPostMessage({
name: Constants.PostMessageNames.closeWindow,
callback: () => {
createGraphPanel.dispose();
}
});
createGraphPanel.registerPostMessage({
name: Constants.PostMessageNames.saveGraph,
callback: async (topology: any) => {
GraphTopologyData.putGraphTopology(this.iotHubData, this.deviceId, this.moduleId, topology).then(
(response) => {
vscode.commands.executeCommand("moduleExplorer.refresh");
createGraphPanel.dispose();
},
(error) => {
// show errors
}
);
}
});
}
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import * as vscode from "vscode";
import { GraphInstanceData } from "../Data/GraphInstanceData";
import { IotHubData } from "../Data/IotHubData";
import { Constants } from "../Util/Constants";
import { CredentialStore } from "../Util/credentialStore";
import { ExtensionUtils, LvaHubConfig } from "../Util/ExtensionUtils";
import { HubItem } from "./HubItem";
import { ModuleItem } from "./ModuleItem";
@ -19,12 +19,11 @@ export default class ModuleExplorer implements vscode.TreeDataProvider<INode> {
if (connectionConfig && connectionConfig.connectionString) {
this._connectionConfig = connectionConfig;
this._iotHubData = new IotHubData(connectionConfig.connectionString);
// TODO add a command to clear connections
} else {
const connectionInfo = await ExtensionUtils.setConnectionString();
this._iotHubData = connectionInfo.iotHubData;
this._connectionConfig = connectionInfo.lvaHubConfig;
this.context.globalState.update(Constants.LvaGlobalStateKey, this._connectionConfig);
CredentialStore.setConnectionInfo(this.context, this._connectionConfig);
}
if (this._iotHubData && this._connectionConfig) {
this._iotHubData = new IotHubData(this._connectionConfig.connectionString);

Просмотреть файл

@ -4,8 +4,8 @@ import { GraphTopologyData } from "../Data/GraphTolologyData";
import { IotHubData } from "../Data/IotHubData";
import { MediaGraphInstance } from "../lva-sdk/lvaSDKtypes";
import { LvaHubConfig } from "../Util/ExtensionUtils";
import { GraphTopologyItem } from "./GraphTopologyItem";
import { INode } from "./Node";
import { TopologyItem } from "./TopologyItem";
export class ModuleItem extends vscode.TreeItem {
constructor(
@ -22,12 +22,10 @@ export class ModuleItem extends vscode.TreeItem {
public getChildren(lvaHubConfig?: LvaHubConfig, graphInstances?: MediaGraphInstance[]): Promise<INode[]> | INode[] {
return new Promise((resolve, reject) => {
GraphTopologyData.getGraphTopologies(this.iotHubData, this.deviceId, this.moduleId).then((graphTopologies) => {
const createGraphItem = new vscode.TreeItem("Create graph");
createGraphItem.iconPath = new vscode.ThemeIcon("add");
resolve([
createGraphItem as any, // Testing in line command
new GraphTopologyItem(this.iotHubData, this.deviceId, this.moduleId),
...graphTopologies?.map((topology) => {
return new TopologyItem(this.iotHubData, this.deviceId, this.moduleId, topology, graphInstances ?? []);
return new GraphTopologyItem(this.iotHubData, this.deviceId, this.moduleId, topology, graphInstances ?? []);
})
]);
});

Просмотреть файл

@ -1,30 +0,0 @@
import * as vscode from "vscode";
import { IotHubData } from "../Data/IotHubData";
import { MediaGraphInstance, MediaGraphTopology } from "../lva-sdk/lvaSDKtypes";
import { InstanceItem } from "./InstanceItem";
import { INode } from "./Node";
export class TopologyItem extends vscode.TreeItem {
constructor(
public iotHubData: IotHubData,
public readonly deviceId: string,
public readonly moduleId: string,
public readonly graphTopology: MediaGraphTopology,
private readonly _graphInstances: MediaGraphInstance[]
) {
super(graphTopology.name, vscode.TreeItemCollapsibleState.Expanded);
this.iconPath = new vscode.ThemeIcon("primitive-square");
}
public getChildren(): Promise<INode[]> | INode[] {
return (
this._graphInstances
?.filter((instance) => {
return instance?.properties?.topologyName === this.graphTopology.name;
})
?.map((instance) => {
return new InstanceItem(instance);
}) ?? []
);
}
}

Просмотреть файл

@ -15,4 +15,11 @@ export class Constants {
};
public static LvaGlobalStateKey = "lvaGlobalStateConfigKey";
public static ExtensionId = "lva-edge-vscode-extension";
// have a copy of this in react code.
public static PostMessageNames = {
closeWindow: "closeWindow",
saveGraph: "saveGraph"
};
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
"use strict";
import * as keytar from "keytar";
import { v4 as uuid } from "uuid";
import * as vscode from "vscode";
import { Constants } from "./constants";
import { DeviceConfig, LvaHubConfig } from "./ExtensionUtils";
interface CredentialLvaHubConfig {
connectionStringKey: string;
devices: DeviceConfig[];
}
export class CredentialStore {
public static async getConnectionInfo(context: vscode.ExtensionContext): Promise<LvaHubConfig> {
const connectionInfo: CredentialLvaHubConfig | undefined = context.globalState.get(Constants.LvaGlobalStateKey);
if (!connectionInfo) {
return (null as unknown) as LvaHubConfig;
}
let connectionString: string | undefined | null = "";
try {
connectionString = await keytar.getPassword(Constants.ExtensionId, connectionInfo.connectionStringKey);
} catch (error) {
connectionString = context.globalState.get(connectionInfo.connectionStringKey);
}
if (!connectionString) {
return (null as unknown) as LvaHubConfig;
}
return { connectionString: connectionString as string, devices: connectionInfo.devices };
}
public static async setConnectionInfo(context: vscode.ExtensionContext, connectionInfo: LvaHubConfig) {
const connectionKey = uuid();
context.globalState.update(Constants.LvaGlobalStateKey, {
connectionStringKey: connectionKey,
devices: connectionInfo.devices
});
try {
await keytar.setPassword(Constants.ExtensionId, connectionKey, connectionInfo.connectionString);
} catch (error) {
context.globalState.update(connectionKey, connectionInfo.connectionString);
}
}
}

Просмотреть файл

@ -9,7 +9,7 @@ export interface LvaHubConfig {
devices: DeviceConfig[];
}
interface DeviceConfig {
export interface DeviceConfig {
deviceId: string;
modules: string[];
}

177
ext/Webview/GraphPanel.ts Normal file
Просмотреть файл

@ -0,0 +1,177 @@
import { filter } from "lodash";
import * as path from "path";
import * as vscode from "vscode";
import Localizer from "../Util/Localizer";
interface PostMessage {
name: string;
callback?: (data?: any) => void;
}
/**
* Manages graph editor webview panels
*/
export class GraphEditorPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: GraphEditorPanel | undefined;
public static readonly viewType = "lvaTopologyEditor";
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string;
private _disposables: vscode.Disposable[] = [];
private _registeredMessages: PostMessage[] = [];
public static createOrShow(extensionPath: string) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it.
if (GraphEditorPanel.currentPanel) {
GraphEditorPanel.currentPanel._panel.reveal(column);
return;
}
// Otherwise, create a new panel.
const panel = vscode.window.createWebviewPanel(GraphEditorPanel.viewType, Localizer.localize("lva-edge.webview.title"), column || vscode.ViewColumn.One, {
// Enable javascript in the webview
enableScripts: true,
// And restrict the webview to only loading content from our extension's `build` directory.
localResourceRoots: [vscode.Uri.file(path.join(extensionPath, "build"))]
});
GraphEditorPanel.currentPanel = new GraphEditorPanel(panel, extensionPath);
return GraphEditorPanel.currentPanel;
}
public static revive(panel: vscode.WebviewPanel, extensionPath: string) {
GraphEditorPanel.currentPanel = new GraphEditorPanel(panel, extensionPath);
}
private constructor(panel: vscode.WebviewPanel, extensionPath: string) {
this._panel = panel;
this._extensionPath = extensionPath;
// Set the webview's initial html content
this._update();
// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programmatically
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Update the content based on view changes
this._panel.onDidChangeViewState(
(e) => {
if (this._panel.visible) {
this._update();
}
},
null,
this._disposables
);
this._panel.webview.onDidReceiveMessage((message) => {
const filteredEvents = this._registeredMessages.filter((event) => {
return event.name === message.command;
});
if (filteredEvents?.length === 1 && filteredEvents[0].callback) {
filteredEvents[0].callback(message.text);
}
});
}
public registerPostMessage(message: PostMessage) {
this._registeredMessages.push(message);
}
public dispose() {
GraphEditorPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
this._panel.title = Localizer.localize("lva-edge.webview.title");
this._panel.webview.html = this._getHtmlForWebview();
}
private _getResourceInjection(nonce: string, ending: string, template: (uri: vscode.Uri) => string) {
const webview = this._panel.webview;
// from the VS Code example, seems to have to be this way instead import
// eslint-disable-next-line @typescript-eslint/no-var-requires
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const fileNames = manifest.entrypoints.filter((fileName: string) => fileName.endsWith("." + ending));
return fileNames
.map((fileName: string) => {
// Local path to main script run in the webview
const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, "build", fileName));
// And the uri we use to load this script in the webview
const uri = webview.asWebviewUri(scriptPathOnDisk);
return template(uri);
})
.join("");
}
private _getHtmlForWebview() {
const webview = this._panel.webview;
// Use a nonce to whitelist which scripts can be run
const nonce = getNonce();
const stylesheetInjection = this._getResourceInjection(nonce, "css", (uri) => `<link nonce="${nonce}" href="${uri}" rel="stylesheet">`);
const scriptInjection = this._getResourceInjection(nonce, "js", (uri) => `<script nonce="${nonce}" src="${uri}"></script>`);
// The linter does not know of this since it is VS Code internal
// so we set a fallback value
const language = JSON.parse(process.env.VSCODE_NLS_CONFIG || "{}")["locale"];
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="img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${Localizer.localize("lva-edge.webview.title")}</title>
${stylesheetInjection}
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}">
__webpack_nonce__ = "${nonce}";
__webpack_public_path__ = "${webview.asWebviewUri(vscode.Uri.file(path.join(this._extensionPath, "build")))}/";
window.language = "${language}";
</script>
${scriptInjection}
</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;
}

Просмотреть файл

@ -1,18 +1,19 @@
import * as path from "path";
import * as vscode from "vscode";
import { GraphTopologyItem } from "./ModuleExplorerPanel/GraphTopologyItem";
import { HubItem } from "./ModuleExplorerPanel/HubItem";
import ModuleExplorer from "./ModuleExplorerPanel/ModuleExplorer";
import { Constants } from "./Util/Constants";
import { LvaHubConfig } from "./Util/ExtensionUtils";
import { CredentialStore } from "./Util/credentialStore";
import Localizer from "./Util/Localizer";
import { GraphEditorPanel } from "./Webview/GraphPanel";
export function activate(context: vscode.ExtensionContext) {
export async function activate(context: vscode.ExtensionContext) {
const locale = JSON.parse(process.env.VSCODE_NLS_CONFIG || "{}")["locale"];
Localizer.loadLocalization(locale, context.extensionPath);
context.subscriptions.push(
vscode.commands.registerCommand("lvaTopologyEditor.start", () => {
GraphEditorPanel.createOrShow(context.extensionPath);
vscode.commands.registerCommand("lvaTopologyEditor.start", (newGraphItem: GraphTopologyItem) => {
//GraphEditorPanel.createOrShow(context.extensionPath);
newGraphItem.createNewGraphCommand(context);
})
);
@ -28,7 +29,7 @@ export function activate(context: vscode.ExtensionContext) {
const moduleExplorer = new ModuleExplorer(context);
vscode.window.registerTreeDataProvider("moduleExplorer", moduleExplorer);
const config = context.globalState.get<LvaHubConfig>(Constants.LvaGlobalStateKey);
const config = await CredentialStore.getConnectionInfo(context);
if (config) {
moduleExplorer.setConnectionString(config);
}
@ -47,156 +48,3 @@ export function activate(context: vscode.ExtensionContext) {
})
);
}
/**
* Manages graph editor webview panels
*/
class GraphEditorPanel {
/**
* Track the currently panel. Only allow a single panel to exist at a time.
*/
public static currentPanel: GraphEditorPanel | undefined;
public static readonly viewType = "lvaTopologyEditor";
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string;
private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionPath: string) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it.
if (GraphEditorPanel.currentPanel) {
GraphEditorPanel.currentPanel._panel.reveal(column);
return;
}
// Otherwise, create a new panel.
const panel = vscode.window.createWebviewPanel(GraphEditorPanel.viewType, Localizer.localize("lva-edge.webview.title"), column || vscode.ViewColumn.One, {
// Enable javascript in the webview
enableScripts: true,
// And restrict the webview to only loading content from our extension's `build` directory.
localResourceRoots: [vscode.Uri.file(path.join(extensionPath, "build"))]
});
GraphEditorPanel.currentPanel = new GraphEditorPanel(panel, extensionPath);
}
public static revive(panel: vscode.WebviewPanel, extensionPath: string) {
GraphEditorPanel.currentPanel = new GraphEditorPanel(panel, extensionPath);
}
private constructor(panel: vscode.WebviewPanel, extensionPath: string) {
this._panel = panel;
this._extensionPath = extensionPath;
// Set the webview's initial html content
this._update();
// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programmatically
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Update the content based on view changes
this._panel.onDidChangeViewState(
(e) => {
if (this._panel.visible) {
this._update();
}
},
null,
this._disposables
);
}
public dispose() {
GraphEditorPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
while (this._disposables.length) {
const x = this._disposables.pop();
if (x) {
x.dispose();
}
}
}
private _update() {
this._panel.title = Localizer.localize("lva-edge.webview.title");
this._panel.webview.html = this._getHtmlForWebview();
}
private _getResourceInjection(nonce: string, ending: string, template: (uri: vscode.Uri) => string) {
const webview = this._panel.webview;
// from the VS Code example, seems to have to be this way instead import
// eslint-disable-next-line @typescript-eslint/no-var-requires
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const fileNames = manifest.entrypoints.filter((fileName: string) => fileName.endsWith("." + ending));
return fileNames
.map((fileName: string) => {
// Local path to main script run in the webview
const scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, "build", fileName));
// And the uri we use to load this script in the webview
const uri = webview.asWebviewUri(scriptPathOnDisk);
return template(uri);
})
.join("");
}
private _getHtmlForWebview() {
const webview = this._panel.webview;
// Use a nonce to whitelist which scripts can be run
const nonce = getNonce();
const stylesheetInjection = this._getResourceInjection(nonce, "css", (uri) => `<link nonce="${nonce}" href="${uri}" rel="stylesheet">`);
const scriptInjection = this._getResourceInjection(nonce, "js", (uri) => `<script nonce="${nonce}" src="${uri}"></script>`);
// The linter does not know of this since it is VS Code internal
// so we set a fallback value
const language = JSON.parse(process.env.VSCODE_NLS_CONFIG || "{}")["locale"];
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="img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${Localizer.localize("lva-edge.webview.title")}</title>
${stylesheetInjection}
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}">
__webpack_nonce__ = "${nonce}";
__webpack_public_path__ = "${webview.asWebviewUri(vscode.Uri.file(path.join(this._extensionPath, "build")))}/";
window.language = "${language}";
</script>
${scriptInjection}
</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;
}

28230
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -10,10 +10,8 @@
"Extension Packs"
],
"activationEvents": [
"onCommand:lvaTopologyEditor.start",
"onWebviewPanel:lvaTopologyEditor",
"onView:moduleExplorer",
"onCommand:moduleExplorer.refresh"
"onView:moduleExplorer"
],
"repository": {
"type": "git",
@ -28,7 +26,7 @@
"category": "%lva-edge.command.category%"
},
{
"command": "moduleExplorer.refreshEntry",
"command": "moduleExplorer.refresh",
"title": "Refresh",
"icon": {
"dark": "ext/resources/dark/refresh.svg",
@ -51,7 +49,7 @@
"menus": {
"view/title": [
{
"command": "moduleExplorer.refreshEntry",
"command": "moduleExplorer.refresh",
"when": "view == moduleExplorer",
"group": "navigation"
}
@ -83,16 +81,18 @@
"refreshVSToken": "vsts-npm-auth -config .npmrc"
},
"dependencies": {
"@types/keytar": "^4.4.2",
"@vienna/react-dag-editor": "^1.73.0",
"azure-iothub": "^1.12.4",
"dagre": "^0.8.5",
"eslint": "^6.8.0",
"keytar": "^6.0.1",
"lodash": "^4.17.19",
"office-ui-fabric-react": "^7.117.1",
"react": "^16.13.1",
"react-accessible-tree": "^1.0.3",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1"
"react-scripts": "^3.4.2"
},
"devDependencies": {
"@types/dagre": "^0.7.44",

Просмотреть файл

@ -6,5 +6,6 @@
"connectionString.prompt": "Enter an IoT Hub connection string",
"deviceList.prompt": "Select a device",
"moduleList.prompt": "Select the live video analytics module",
"iotHub.connectionString.validationMessageFormat": "The format should be :"
"iotHub.connectionString.validationMessageFormat": "The format should be :",
"createGraphButton": "Create graph"
}

Просмотреть файл

@ -3,7 +3,6 @@ import { ITheme } from "office-ui-fabric-react";
import { ThemeProvider } from "office-ui-fabric-react/lib/Foundation";
import React, { useEffect } from "react";
import { IZoomPanSettings } from "@vienna/react-dag-editor";
import { sampleTopology } from "./dev/sampleTopologies.js";
import { GraphInstance } from "./editor/components/GraphInstance";
import { GraphTopology } from "./editor/components/GraphTopology";
import Graph from "./graph/Graph";
@ -38,8 +37,6 @@ export const App: React.FunctionComponent<IProps> = (props) => {
if (props.graphData) {
graph.setGraphData(props.graphData);
} else {
graph.setTopology(sampleTopology);
}
// if there is no state to recover from (in props.graphData or zoomPanSettings), use default

Просмотреть файл

@ -11,6 +11,7 @@ import {
RegisterPort,
withDefaultPortsPosition
} from "@vienna/react-dag-editor";
import { ExtensionInteraction } from "../../extension/extensionInteraction";
import Graph from "../../graph/Graph";
import Localizer from "../../localization/Localizer";
import { graphTheme as theme } from "../editorTheme";
@ -83,6 +84,10 @@ export const GraphTopology: React.FunctionComponent<IGraphTopologyProps> = (prop
graph.setDescription(graphDescription);
graph.setGraphDataFromICanvasData(data);
const topology = graph.getTopology();
const vscode = ExtensionInteraction.getVSCode();
if (vscode) {
vscode.postMessage({ command: "saveGraph", text: topology });
}
console.log(topology);
};
@ -152,7 +157,13 @@ export const GraphTopology: React.FunctionComponent<IGraphTopologyProps> = (prop
name={graphTopologyName}
exportGraph={exportGraph}
closeEditor={() => {
alert("TODO: Close editor");
console.log("try this one");
const vscode = ExtensionInteraction.getVSCode();
if (vscode) {
vscode.postMessage({
command: "closeWindow"
});
}
}}
/>
<Stack.Item grow>

Просмотреть файл

@ -1,34 +1,42 @@
import Localizer from "../localization/Localizer";
import { InitializationParameters } from "../types/vscodeDelegationTypes";
// VS Code exposes this function: https://code.visualstudio.com/api/references/vscode-api#WebviewPanelSerializer
declare const acquireVsCodeApi: any;
export class ExtensionInteraction {
private static _vsCode: vscode;
export async function initalizeEnvironment(language: string) {
await Localizer.loadUserLanguage(language);
return new Promise((resolve: (params: InitializationParameters) => void, reject) => {
// Check if this is running in VS Code (might be developing in React)
if (typeof acquireVsCodeApi === "function") {
(function () {
const vscode = acquireVsCodeApi();
const oldState = vscode.getState() || {};
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data;
// use message.command
});
resolve({
state: oldState,
vsCodeSetState: vscode.setState
});
})();
} else {
// We won't save/restore state in browser, use noop function
// eslint-disable-next-line @typescript-eslint/no-empty-function
resolve({ state: {}, vsCodeSetState: () => {} });
public static getVSCode() {
if (typeof this._vsCode == "undefined" && typeof acquireVsCodeApi == "function") {
this._vsCode = acquireVsCodeApi();
}
});
return this._vsCode;
}
public static async initializeEnvironment(language: string) {
await Localizer.loadUserLanguage(language);
return new Promise((resolve: (params: InitializationParameters) => void, reject) => {
// Check if this is running in VS Code (might be developing in React)
const vscode = this.getVSCode();
if (vscode) {
(function () {
const oldState = vscode.getState() || {};
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data;
// use message.command
});
resolve({
state: oldState,
vsCodeSetState: vscode.setState
});
})();
} else {
// We won't save/restore state in browser, use noop function
// eslint-disable-next-line @typescript-eslint/no-empty-function
resolve({ state: {}, vsCodeSetState: () => {} });
}
});
}
}

Просмотреть файл

@ -3,10 +3,10 @@ import "./scripts/formatString";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { initalizeEnvironment } from "./extension/extensionInteraction";
import { ExtensionInteraction } from "./extension/extensionInteraction";
import { InitializationParameters } from "./types/vscodeDelegationTypes";
initalizeEnvironment((window as any).language).then((params: InitializationParameters) => {
ExtensionInteraction.initializeEnvironment((window as any).language).then((params: InitializationParameters) => {
// if we are running in VS Code and have stored state, we can recover it from state
// vsCodeSetState will allow for setting that state
// saving and restoring state happens when the webview loses and regains focus

7
src/types/globals.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
interface vscode {
postMessage(message: { command: string; text?: any }): void;
getState();
setState(state: any);
}
declare const acquireVsCodeApi;