Theia 窗口组件 widget
组件widget是在 Theia 工作台中显示内容的部分,例如 视图或编辑器。 Theia 中的文件浏览器、代码编辑器或问题视图都是组件。 通过自定义组件,您可以将自己的自定义 UI 放置在基于 Theia 的应用程序中。 您的自定义 UI 在窗口布局方面与其他组件的行为相同,包括标题选项卡、调整大小、拖动和打开/关闭(请参见下面的屏幕截图)。
此外,组件将接收来自周围工作台的事件,例如 在应用程序启动、调整大小或分离时。 但是,在提供的框架中呈现的组件的实际内容的实现完全取决于您。 例如,您可以在组件中使用 React 实现一些自定义 UI。
简而言之,组件是将一些自定义(基于 HTML)UI 嵌入到 Theia 工作台中的框架(见下图)
在本文中,我们将描述如何向 Theia 工作台贡献自定义组件。 我们将专注于一个简单的视图(与编辑器相比)并使用 React 来实现 UI。
如果您还不熟悉 Theia 中的贡献点或依赖注入的使用,请参考有关服务和贡献的指南(https://www.cnblogs.com/theia-ide/p/16476556.html)。
如果您想查看示例代码,请使用 Theia 扩展生成器。 安装生成器,选择“Widget”示例并输入“MyWidget”作为扩展名。具体的示例代码来源可参考这里:https://www.cnblogs.com/theia-ide/p/16494835.html。
示例代码
本文后续讲到的代码,来自扩展生成器自动生成的代码。大体流程就是安装代码生成器后执行:
npm install -g yo generator-theia-extension mkdir theia-widget cd theia-widget yo theia-extension
在选择扩展类型时,选择Widget这一项:
之后就会生成一套完整可运行且包含一个自定义widget的组件了。
运行示例代码
开两个终端,进入上面创建的theia-widget目录。
第一个终端执行:yarn watch
这样的话,代码修改了后会重新编译
第二个终端执行:yarn start:browser
服务会启动并监听3000端口。浏览器访问3000端口可见页面,view菜单下会出现我们新添加的子菜单:
创建一个组件(一个视图)
创建一个组件,在我们的示例中,一个视图由三个组件组成:
1. 组件,负责:
- 设置基本参数,例如 ID、标签和图标
- 创建实际的 UI 及其行为
- 响应生命周期事件,例如 `onUpdateRequest` 或 `onResize`
2. 一个组件工厂,负责实例化组件
3. widget contribution,负责将视图与 Theia 工作台连接,以便可以从 Theia 工作台内打开组件,例如通过view菜单
实现一个组件
对于自定义组件的实现,Theia 提供了几个可以继承的基类。这使您可以只专注于创建自定义 UI,因为基类已经实现了大多数必需的功能。 Theia 不依赖于特定的 UI 技术,例如 React、Vue.js 或 Angular。但是,它通过提供相应的基类来简化开发。以React为例,使用 React 是目前实现自定义组件的最常见选择。您可以在下面找到类层次结构的摘录。如果您想使用 react 实现一个组件,请选择 ReactWidget 作为基类。如果要实现主要显示树的小部件,请使用 TreeWidget。如果您不想使用 React,请使用 BaseWidget。浏览 BaseWidget 的类型层次结构以查看其他可用选项。
在下面的代码示例中,我们使用 ReactWidget 作为基类。如下所示,我们首先用一些基本参数初始化组件:
id:唯一标识组件,例如通过 WidgetManager 打开它。
label:组件打开时显示在选项卡中。
caption:当小部件打开时将鼠标悬停在选项卡上时显示。
closable:用户是否可以关闭小部件(通过选项卡中的“x”或通过右键菜单)。
iconClass:小部件打开时选项卡中显示的图标。
mywidget-widget.ts
@injectable() export class MyWidget extends ReactWidget { static readonly ID = 'my:widget'; static readonly LABEL = 'My Widget'; @postConstruct() protected async init(): Promise < void> { this.id = MyWidget.ID; this.title.label = MyWidget.LABEL; this.title.caption = MyWidget.LABEL; this.title.closable = true; this.title.iconClass = 'fa fa-window-maximize'; // example widget icon. this.update(); }
使用相应的基类时,小部件的实现可以非常简单,并且专注于自定义 UI 部分。 在我们的示例中,我们只实现了自定义 UI(使用 JSX/React)的渲染函数。 示例 UI 包含一个按钮,该按钮将触发下面的 displayMessage 函数。
mywidget-widget.ts
protected render(): React.ReactNode { const header = `This is a sample widget which simply calls the messageService in order to display an info message to end users.`; return <div id='widget-container'> <AlertMessage type='INFO' header={header} /> <button className='theia-button secondary' title='Display Message' onClick={_a => this.displayMessage()}>Display Message</button> </div> } @inject(MessageService) protected readonly messageService!: MessageService; protected displayMessage(): void { this.messageService.info('Congratulations: My Widget Successfully Created!'); }
请注意,您可以重写 BaseWidget 或 ReactWidget 的函数以重写组件的特定生命周期事件,例如onUpdateRequest 或 onResize。这些事件由底层窗口管理框架 Phosphor.js(https://phosphorjs.github.io/) 定义,请参阅有关 Widget 类的文档(http://phosphorjs.github.io/phosphor/api/widgets/classes/widget.html)。
除了实现实际的组件之外,您还需要将它与 Eclipse Theia 工作台连接起来,这将在接下来的两节中进行描述。
实现一个组件工厂
Theia 中的组件由中央服务 WidgetManager 实例化和管理。这允许应用程序跟踪所有创建的组件。例如,WidgetManager 支持函数 getOrCreate,如果它已经创建,它将返回一个现有的组件,或者如果没有创建一个新的组件。
要使WidgetManager可实例化自定义组件,您需要注册一个 WidgetFactory。WidgetFactory由一个 ID 和一个创建实际组件的函数组成。组件管理器将收集所有贡献的组件工厂,并按 ID 为相应的组件选择正确的工厂。
在我们的示例中(参见下面的代码),我们首先将组件 MyWidget 绑定到自身,以便我们可以在工厂中使用依赖注入来实例化它。如果它们内部不使用依赖注入,则这不是必须的。我们在上面的示例中使用依赖注入来检索message service和@postConstruct 事件。其次,我们绑定一个定义组件ID 的 WidgetFactory 和 createWidget 函数。此功能允许您控制组件的创建,例如如果需要,将特定参数传递给自定义组件。在我们的示例中,我们只是使用依赖注入上下文来实例化我们的组件。
mywidget-frontend-module.t
bind(MyWidget).toSelf(); bind(WidgetFactory).toDynamicValue(ctx => ({ id: MyWidget.ID, createWidget: () => ctx.container.get<MyWidget>(MyWidget) })).inSingletonScope();
现在您已经可以通过使用WidgetManager API 手动打开组件。但是,对于大多数情况下,您希望将视图添加到视图菜单并提供相应的命令。这可以使用下一节中描述的组件贡献来方便地完成。
组件贡献
组件贡献允许您将组件连接到 Theia 工作台,更准确地说是将它们添加到视图菜单和快速命令栏。 Theia 提供了一个方便的基类 AbstractViewContribution 来继承,它已经实现了最常见的功能集(参见下面的示例代码)。对于初始化,您只需要指定以下参数:
widgetID:组件的ID,用于通过WidgetManager打开它
widgetName:显示在视图菜单中的名称。通常与组件选项卡使用的名称相同。
defaultWidgetOptions:影响组件在打开时显示位置的选项,例如在工作台的左侧区域。有关更多信息,请参阅 typedoc(https://www.cnblogs.com/theia-ide/p/16527862.html)。
toggleCommandId:打开视图的命令。您可以使用超类提供的预先实现的 openView 功能。除了指定这些基本参数外,您还需要注册打开视图的命令。基类实现了相应的命令贡献接口,因此您只需要实现 registerCommands 即可(见下文)。
mywidget-contribution.ts
export const MyWidgetCommand: Command = { id: 'widget:command' }; export class MyWidgetContribution extends AbstractViewContribution<MyWidget> { constructor() { super({ widgetId: MyWidget.ID, widgetName: MyWidget.LABEL, defaultWidgetOptions: { area: 'left' }, toggleCommandId: MyWidgetCommand.id }); } registerCommands(commands: CommandRegistry): void { commands.registerCommand(WidgetCommand, { execute: () => super.openView({ activate: false, reveal: true }) }); } }
有了上面的贡献,视图现在将显示在 Theia 的标准“视图”菜单中,您也可以使用相应的“打开视图”命令打开它。