- Panel // 面板,wrapper around PanelLayout
- BoxPanel // wrapper around a BoxLayout , 将子 widgets 按照行或列的方式排列
- SplitPanel // wrapper around a SplitLayout , arranges its widgets into resizable sections.
- StackedPanel // wrapper around a StackedLayout , visible widgets are stacked atop one another
- CommandPalette // displays command items as a searchable palette
- Menu // displays items as a canonical menu
- TabBar // displays titles as a single row or column of tabs
- DockPanel // 提供灵活的 docking area
- MenuBar // canonical menu bar
- ScrollBar // canonical scroll bar
- TabPanel // combines a TabBar and a StackedPanel
/**
* The namespace for the `Widget` class statics.
*/
export declare namespace Widget {
/**
* Construct a new widget.
*
* @param options - The options for initializing the widget.
*/
constructor(options?: Widget.IOptions);
/**
* Get the DOM node owned by the widget.
*/
readonly node: HTMLElement;
readonly title: Title<Widget>;
parent: Widget | null;
layout: Layout | null;
children(): IIterator<Widget>;
/**
* Post an `'update-request'` message to the widget.
*
* #### Notes
* This is a simple convenience method for posting the message.
*/
update(): void;
/**
* Attach a widget to a host DOM node.
*
* @param widget - The widget of interest.
*
* @param host - The DOM node to use as the widget's host.
*
* @param ref - The child of `host` to use as the reference element.
* If this is provided, the widget will be inserted before this
* node in the host. The default is `null`, which will cause the
* widget to be added as the last child of the host.
*/
function attach(widget: Widget, host: HTMLElement, ref?: HTMLElement | null): void;
}
// packages/core/src/browser/frontend-application.ts
@injectable()
export class FrontendApplication {
/**
* Start the frontend application.
*
* Start up consists of the following steps:
* - start frontend contributions
* - attach the application shell to the host element
* - initialize the application shell layout
* - reveal the application shell if it was hidden by a startup indicator
*/
async start(): Promise<void> {
await this.startContributions();
this.stateService.state = 'started_contributions';
const host = await this.getHost();
this.attachShell(host);
await animationFrame();
this.stateService.state = 'attached_shell';
await this.initializeLayout();
this.stateService.state = 'initialized_layout';
await this.fireOnDidInitializeLayout();
await this.revealShell(host);
this.registerEventListeners();
this.stateService.state = 'ready';
}
/**
* Attach the application shell to the host element. If a startup indicator is present, the shell is
* inserted before that indicator so it is not visible yet.
*/
protected attachShell(host: HTMLElement): void {
const ref = this.getStartupIndicator(host);
Widget.attach(this.shell, host, ref); // 本质是调用 host.insertBefore(widget.node, ref);
}
}
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { injectable, unmanaged } from 'inversify';
import { DisposableCollection, Disposable } from '../../common';
import { BaseWidget, Message } from './widget';
import { Widget } from '@phosphor/widgets';
@injectable()
export abstract class ReactWidget extends BaseWidget {
protected readonly onRender = new DisposableCollection();
constructor(@unmanaged() options?: Widget.IOptions) {
super(options);
this.scrollOptions = {
suppressScrollX: true,
minScrollbarLength: 35,
};
this.toDispose.push(Disposable.create(() => {
ReactDOM.unmountComponentAtNode(this.node);
}));
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
ReactDOM.render(<React.Fragment>{this.render()}</React.Fragment>, this.node, () => this.onRender.dispose());
}
/**
* Render the React widget in the DOM.
* - If the widget has been previously rendered,
* any subsequent calls will perform an update and only
* change the DOM if absolutely necessary.
*/
protected abstract render(): React.ReactNode;
}
FrontendApplication.start()
构建的Theia 框架前端 UI 布局和 Services 一样,具备灵活可拓展的特点。VSCode 是内置了一套基本的组件系统,而 Theia 框架的 UI 布局基于 PhosphorJS 框架。 PhosphorJS 提供了包含 widgets、layouts、事件和数据结构的丰富工具包。这使得开发人员能够构建可扩展的、高性能的、类桌面的 Web 应用程序,比如 JupyterLab。
效果
example-dockpanel
在 PhosphorJS 里运行 React 代码:ermalism/phosphorjs-react-jsx-example
Widget
PhosphorJS 布局的核心就在于 Widget。
这里的 Widget 和 Flutter 里面的 Widget 还不一样,Flutter 的 Widget 属于声明式 UI(declarative UI),而 PhosphorJS 的 Widget 更像是命令式 UI(imperative UI)。和 Chrome 开发者工具 ChromeDevTools/devtools-frontend 的 Widget 更类似。
关于声明式和命令式 UI 框架也可以阅读:聊聊我对现代前端框架的认知 作为补充。
Widget 的继承
官方提供了一系列 Widget 的继承实现:
Widget
并且都实现了 IDisposable 和 IMessageHandler 接口。
接口
Widget 包含以下状态:isDisposed、isAttached、isHidden、isVisible,以及一系列事件驱动的钩子:onCloseRequest、onResize、onUpdateRequest、onFitRequest、onActivateRequest、onBeforeShow、onBeforeHide、onBeforeAttach、onBeforeDetach、onChildAdded 等。
渲染的核心的方法在于
Widget.attach
,本质上就是:host.insertBefore(widget.node, ref);
。Widget 主要的字段及接口如下:
Theia 布局构建
Theia 前端页面的启动非常简单:
可以看到核心就在于
FrontendApplication.start()
方法,那么这个方法里做了什么?FrontendApplication
ApplicationShell
主要分为:mainPanel:TheiaDockPanel、topPanel:Panel、bottomPanel:TheiaDockPanel、leftPanel、rightPanel
在 ApplicationShell 中初始化并拼装。
Plugin API 里的 Widget 创建
Node/Browser API 的 Widget 创建:通过 WidgetFactory。
Packages
commands
Class CommandRegistry 管理命令集合的对象。用于 CommandRegistry 类 statics 的命名空间。
命令注册表可用于填充各种 action-based widgets,如命令 palettes、menus 和 toolbars。
Widgets 与 React
将 React 组件封装成 Widget 组件
思路:
然后当作自定义的 Widget 使用即可。
Theia 已提供抽象组件 ReactWidgt 供参考:packages/core/src/browser/widgets/react-widget.tsx
将 Widget 封装成 React 组件
或者参考:Run a PhosphorJS DockerPanel with Widgets INSIDE a React component
参考