refactor🎨: (阅读代码):webview.ts增加注释

master
yetao 2 weeks ago
parent 2c6e892b66
commit 820c5196b3

@ -1,132 +1,251 @@
// 导入 common/fileSystemConfig 模块中的 getLocations 函数
import { getLocations } from 'common/fileSystemConfig';
// 导入 common/webviewMessages 模块中的 Message 和 Navigation 类型
import type { Message, Navigation } from 'common/webviewMessages';
// 导入 fs 模块,用于文件系统操作
import * as fs from 'fs';
// 导入 path 模块,用于路径操作
import * as path from 'path';
// 导入 vscode 模块,用于 VS Code 扩展 API
import * as vscode from 'vscode';
// 导入 config 模块中的 deleteConfig、getConfigs、loadConfigs、updateConfig 函数
import { deleteConfig, getConfigs, loadConfigs, updateConfig } from './config';
// 导入 logging 模块中的 DEBUG、LOGGING_NO_STACKTRACE、Logging 类型
import { DEBUG, LOGGING_NO_STACKTRACE, Logging as _Logging } from './logging';
// 导入 utils 模块中的 toPromise 函数
import { toPromise } from './utils';
// 使用 _Logging 模块创建一个名为 'WebView' 的日志记录器
const Logging = _Logging.scope('WebView');
// 存储当前激活的 WebView 面板实例,用于在扩展中与 WebView 进行交互
let webviewPanel: vscode.WebviewPanel | undefined;
// 存储待处理的导航信息,当用户在 WebView 中点击链接或按钮时,可能会触发导航事件
let pendingNavigation: Navigation | undefined;
/**
* VS Code
* @returns undefined
*/
function getExtensionPath(): string | undefined {
// 使用 vscode.extensions.getExtension 方法获取名为 'Kelvin.vscode-sshfs' 的扩展
const ext = vscode.extensions.getExtension('Kelvin.vscode-sshfs');
// 如果找到了扩展,则返回扩展的路径,否则返回 undefined
return ext && ext.extensionPath;
}
/**
*
* @returns false
* @description
* DEBUG false
* URL http://localhost:3000/)。
* 使 Node.js http URL GET
* 200
* 200 body
* CSP
*
*/
async function getDebugContent(): Promise<string | false> {
// 如果未启用 DEBUG 模式,则直接返回 false
if (!DEBUG) return false;
// 构建 React 开发服务器的 URL
const URL = `http://localhost:3000/`;
// 异步导入 http 模块
const http = await import('http');
// 发送 GET 请求到 React 开发服务器,并处理响应
return toPromise<string>(cb => http.get(URL, async (message) => {
if (message.statusCode !== 200) return cb(new Error(`Error code ${message.statusCode} (${message.statusMessage}) connecting to React dev server}`));
// 如果响应状态码不是 200则返回错误
if (message.statusCode!== 200) return cb(new Error(`Error code ${message.statusCode} (${message.statusMessage}) connecting to React dev server`));
// 初始化响应 body 字符串
let body = '';
// 当接收到数据块时,将其添加到 body 字符串中
message.on('data', chunk => body += chunk);
// 等待数据接收完毕
await toPromise(cb => message.on('end', cb));
// 替换 body 字符串中的资源路径
body = body.toString().replace(/\/static\/js\/bundle\.js/, `${URL}/static/js/bundle.js`);
// Make sure the CSP meta tag also includes the React dev server (including connect-src for the socket, which uses both http:// and ws://)
// 确保 CSP 元数据包含 React 开发服务器的 URL
body = body.replace(/\$WEBVIEW_CSPSOURCE/g, `$WEBVIEW_CSPSOURCE ${URL}`);
// 添加额外的 CSP 规则,允许连接到 React 开发服务器的 socket
body = body.replace(/\$WEBVIEW_CSPEXTRA/g, `connect-src ${URL} ${URL.replace('http://', 'ws://')};`);
// 替换 body 字符串中的资源路径前缀
body = body.replace(/src="\/static\//g, `src="${URL}/static/`);
// 回调函数,返回处理后的 body 字符串
cb(null, body);
// 处理请求过程中的错误
}).on('error', err => {
// 记录错误信息
Logging.warning(`Error connecting to React dev server: ${err}`);
// 返回错误信息
cb(new Error('Could not connect to React dev server. Not running?'));
}));
}
/**
* WebView SSH-FS
* @description
* WebView SSH-FS
* WebView
* WebView
* WebView HTML WebView
* HTML CSP
* WebView
*/
export async function open() {
// 检查是否已经存在 WebView 面板实例
if (!webviewPanel) {
// 获取扩展的路径
const extensionPath = getExtensionPath();
// 创建一个新的 WebView 面板实例
webviewPanel = vscode.window.createWebviewPanel('sshfs-settings', 'SSH-FS', vscode.ViewColumn.One, { enableFindWidget: true, enableScripts: true });
// 当 WebView 面板被关闭时,将 webviewPanel 变量设置为 undefined
webviewPanel.onDidDispose(() => webviewPanel = undefined);
// 如果扩展路径存在,则设置 WebView 面板的图标
if (extensionPath) webviewPanel.iconPath = vscode.Uri.file(path.join(extensionPath, 'resources/icon.svg'));
// 获取 WebView 实例
const { webview } = webviewPanel;
// 监听 WebView 接收到的消息
webview.onDidReceiveMessage(handleMessage);
// 尝试获取调试内容
let content = await getDebugContent().catch((e: Error) => (vscode.window.showErrorMessage(e.message), null));
// 如果没有获取到调试内容
if (!content) {
// 如果扩展路径不存在,则抛出错误
if (!extensionPath) throw new Error('Could not get extensionPath');
// If we got here, we're either not in debug mode, or something went wrong (and an error message is shown)
// 读取本地文件系统中的静态 HTML 文件
content = fs.readFileSync(path.resolve(extensionPath, 'webview/build/index.html')).toString();
// Built index.html has e.g. `href="/static/js/stuff.js"`, need to make it use vscode-resource: and point to the built static directory
// Scrap that last part, vscode-resource: is deprecated and we need to use Webview.asWebviewUri
//content = content.replace(/\/static\//g, vscode.Uri.file(path.join(extensionPath, 'webview/build/static/')).with({ scheme: 'vscode-resource' }).toString());
// 替换 HTML 中的资源路径,使其指向 WebView 的资源
content = content.replace(/\/static\//g, webview.asWebviewUri(vscode.Uri.file(path.join(extensionPath, 'webview/build/static/'))).toString());
}
// Make sure the CSP meta tag has the right cspSource
// 替换 HTML 中的 CSP 元数据
content = content.replace(/\$WEBVIEW_CSPSOURCE/g, webview.cspSource);
// The EXTRA tag is used in debug mode to define connect-src. By default we can (and should) just delete it
// 移除 HTML 中的额外 CSP 规则
content = content.replace(/\$WEBVIEW_CSPEXTRA/g, '');
// 设置 WebView 面板的内容
webview.html = content;
}
// 显示 WebView 面板
webviewPanel.reveal();
}
/**
*
* @param navigation -
* @returns Promise
* @description
* WebView WebView
*
* `pendingNavigation` 便
* WebView WebView
* `open` WebView SSH-FS
* Promise
*/
export async function navigate(navigation: Navigation) {
// 记录接收到导航请求的调试信息
Logging.debug`Navigation requested: ${navigation}`;
// 将导航请求存储在 pendingNavigation 变量中
pendingNavigation = navigation;
// 向 WebView 发送导航请求消息
postMessage({ navigation, type: 'navigate' });
// 打开 WebView 面板
return open();
}
/**
* WebView
* @param message - Message
* @description
* WebView `webviewPanel` 使 `webviewPanel.webview.postMessage`
* `message` `Message`
*/
function postMessage<T extends Message>(message: T) {
// 检查 webviewPanel 是否存在,如果存在则发送消息
webviewPanel?.webview.postMessage(message);
}
/**
* WebView
* @param message - WebView
* @returns Promise
* @description
* WebView `webviewPanel`
* `pendingNavigation` `undefined`
* `requestData``saveConfig``promptPath` `navigated`
* - `requestData` WebView
* - `saveConfig`
* - `promptPath` WebView
* - `navigated` WebView
* Promise
*/
async function handleMessage(message: Message): Promise<any> {
// 如果 webviewPanel 不存在,则记录警告信息
if (!webviewPanel) return Logging.warning`Got message without webviewPanel: ${message}`;
// 记录接收到的消息的调试信息
Logging.debug`Got message: ${message}`;
// 如果存在待处理的导航请求
if (pendingNavigation) {
// 发送导航消息
postMessage({
type: 'navigate',
navigation: pendingNavigation,
});
// 重置 pendingNavigation 变量
pendingNavigation = undefined;
}
// 根据消息的类型进行相应的处理
switch (message.type) {
case 'requestData': {
// 请求配置数据
const configs = await (message.reload ? loadConfigs : getConfigs)();
// 获取配置数据的位置信息
const locations = getLocations(configs);
// 返回配置数据和位置信息
return postMessage({
configs, locations,
type: 'responseData',
});
}
case 'saveConfig': {
// 保存配置数据
const { uniqueId, config, name, remove } = message;
let error: string | undefined;
try {
// 根据 remove 标志决定是删除还是更新配置数据
if (remove) {
await deleteConfig(config);
} else {
await updateConfig(config, name);
}
} catch (e) {
// 记录错误信息
Logging.error('Error handling saveConfig message for settings UI:', LOGGING_NO_STACKTRACE);
Logging.error(JSON.stringify(message), LOGGING_NO_STACKTRACE);
Logging.error(e);
error = e.message;
}
// 返回操作结果
return postMessage({
uniqueId, config, error,
type: 'saveConfigResult',
});
}
case 'promptPath': {
// 提示用户选择路径
const { uniqueId } = message;
let uri: vscode.Uri | undefined;
let error: string | undefined;
try {
// 显示文件选择对话框
const uris = await vscode.window.showOpenDialog({});
if (uris) [uri] = uris;
} catch (e) {
// 记录错误信息
Logging.error`Error handling promptPath message for settings UI:\n${message}\n${e}`;
error = e.message;
}
// 返回用户选择的路径
return postMessage({
uniqueId,
path: uri && uri.fsPath,
@ -134,9 +253,11 @@ async function handleMessage(message: Message): Promise<any> {
});
}
case 'navigated': {
// 导航事件
const { view } = message;
type View = 'startscreen' | 'newconfig' | 'configeditor' | 'configlocator';
let title: string | undefined;
// 根据导航目标更新 WebView 面板的标题
switch (view as View) {
case 'configeditor':
title = 'SSH FS - Edit config';
@ -148,6 +269,7 @@ async function handleMessage(message: Message): Promise<any> {
title = 'SSH FS - New config';
break;
}
// 设置 WebView 面板的标题
webviewPanel.title = title || 'SSH FS';
}
}

Loading…
Cancel
Save