在上篇文章 脚手架源码剖析 文章中,咱们剖析了发动过程中前端页面是怎么展现的,那么本篇文章咱们介绍一下theia布局的相关内容以及怎么自界说布局。
PhosphorJS
Theia的组件和布局体系是运用PhosphorJS完成的,PhosphorJS供给了一组丰富的组件、布局、事情和数据结构。这些使开发人员能够构建高质量的、类桌面的 Web 应用程序。Theia为什么要用PhosphorJS作为布局体系呢?在IDE 应用程序中的选项卡式和停靠式面板,这些类型的交互必须运用 JavaScript 完成,而且以可扩展且优雅的办法完成动态增加数量的模式,这就包含音讯传递、调整巨细/附加/别离/显现/隐藏事情、巨细约束聚合和高效布局核算。PhosphorJS 以一种灵敏、独立且与现有代码兼容的办法供给了这些目前在web上缺少的能力。
Github地址:github.com/phosphorjs/…,文档地址:phosphorjs.github.io/。不过PhosphorJS 作者退休,项目已归档,该项目现在被 Jupyter 团队重命名为 jupyterlab/lumino,Github地址为:github.com/jupyterlab/… 。
怎么完成的?
- PhosphorJS供给了一个简略而灵敏的小部件类,它为音讯传递和DOM节点操作建立了层次结构。这答应在整个层次结构中传达各种音讯,例如:调整巨细、附加、别离、显现和隐藏(以及其他功用)
- 一旦建立了可靠传达的调整巨细音讯,就有可能在JavaScript中完成布局,这是单独运用CSS无法完成的。经过以绝对值清晰指定节点的方位和巨细,浏览器能够优化回流,使其包含在页面的受影响部分中。这意味着对应用程序一部分的更改不会导致整个页面的回流本钱。
- PhosphorJS认识到CSS在许多方面都很好,而且不会阻挠开发人员在适当的时候运用它。PhosphorJS布局与规范CSS布局配合得很好,两者能够在小部件层次结构中自在混合。
- PhosphorJS认识到开发人员最喜欢的结构十分适合特定任务。Phosphor Widget实例能够托管由任何其他结构生成的DOM内容,而且这样的能够自在嵌Widget入任何Phosphor Widget层次结构中。
- PhosphorJS供给了大量预界说的小部件和布局,这些部件和布局很难正确有效地完成,例如:菜单和菜单栏、拆分面板、选项卡和停靠面板。这使得创立前面描绘的富桌面风格应用程序变得简略。
@phosphor/widgets供给了许多布局和组件:
- BoxLayout
- BoxPanel
- DockLayout
- DockPanel
- Menu
- MenuBar
- Panel
- PanelLayout
- TabBar
其间像BoxLayout、DockLayout都是承继layout,像BoxPanel、MenuBar、TabBar等都是承继Widget。Widget有许多的生命周期回调函数:
- onActivateRequest
- onBeforeShow
- onAfterShow
- onBeforeHide
- onAfterHide
- onBeforeAttach
- onAfterAttach
- onBeforeDetach
- onAfterDetach
- onChildAdded
- onChildRemoved
- onCloseRequest
- onResize
- onUpdateRequest
- onFitRequest
经过attach办法,将widget刺进到dom节点中。attach完成如下:
//@phosphor/widgets/src/widget.ts
export
function attach(widget: Widget, host: HTMLElement, ref: HTMLElement | null = null): void {
if (widget.parent) {
throw new Error('Cannot attach a child widget.');
}
if (widget.isAttached || document.body.contains(widget.node)) {
throw new Error('Widget is already attached.');
}
if (!document.body.contains(host)) {
throw new Error('Host is not attached.');
}
MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach);
host.insertBefore(widget.node, ref);
MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach);
}
终究调用host.insertBefore刺进到ref节点前。
在之前脚手架剖析中,咱们最后看到FrontendApplication的start办法发动首要做了这样几件事:1、初始化并发动frontend application contributions,2、调用@phosphor/widgets的Widget.attach办法,将ApplicationShell布局刺进到document.body中class为theia-preload的节点前,3、初始化ApplicationShell的布局,4、隐藏发动动画,展现页面。
//@theia/core/src/browser/frontend-application.ts
get shell(): ApplicationShell {
return this._shell;
}
protected attachShell(host: HTMLElement): void {
const ref = this.getStartupIndicator(host);
Widget.attach(this.shell, host, ref);
}
其间shell是ApplicationShell,接下来详细介绍一下ApplicationShell。
ApplicationShell
Theia整个视图布局首要包含topPanel、leftPanel、mainPanel、rightPanel、bottomPanel和statusBar。
ApplicationShell承继了Widget,在ApplicationShell中别离界说了以上几个视图,在createLayout办法中运用@phosphor/widgets供给的布局容器进行拼装。
//@theia/core/src/browser/shell/application-shell.ts
@injectable()
export class ApplicationShell extends Widget {
/**
* The dock panel in the main shell area. This is where editors usually go to.
*/
mainPanel: TheiaDockPanel;
/**
* The dock panel in the bottom shell area. In contrast to the main panel, the bottom panel
* can be collapsed and expanded.
*/
bottomPanel: TheiaDockPanel;
/**
* Handler for the left side panel. The primary application views go here, such as the
* file explorer and the git view.
*/
leftPanelHandler: SidePanelHandler;
/**
* Handler for the right side panel. The secondary application views go here, such as the
* outline view.
*/
rightPanelHandler: SidePanelHandler;
/**
* General options for the application shell.
*/
protected options: ApplicationShell.Options;
/**
* The fixed-size panel shown on top. This one usually holds the main menu.
*/
topPanel: Panel;
protected initializeShell(): void {
this.addClass(APPLICATION_SHELL_CLASS);
this.id = 'theia-app-shell';
// Merge the user-defined application options with the default options
this.options = {
bottomPanel: {
...ApplicationShell.DEFAULT_OPTIONS.bottomPanel,
...this.options?.bottomPanel || {}
},
leftPanel: {
...ApplicationShell.DEFAULT_OPTIONS.leftPanel,
...this.options?.leftPanel || {}
},
rightPanel: {
...ApplicationShell.DEFAULT_OPTIONS.rightPanel,
...this.options?.rightPanel || {}
}
};
this.mainPanel = this.createMainPanel();
this.topPanel = this.createTopPanel();
this.bottomPanel = this.createBottomPanel();
this.leftPanelHandler = this.sidePanelHandlerFactory();
this.leftPanelHandler.create('left', this.options.leftPanel);
this.leftPanelHandler.dockPanel.widgetAdded.connect((_, widget) => this.fireDidAddWidget(widget));
this.leftPanelHandler.dockPanel.widgetRemoved.connect((_, widget) => this.fireDidRemoveWidget(widget));
this.rightPanelHandler = this.sidePanelHandlerFactory();
this.rightPanelHandler.create('right', this.options.rightPanel);
this.rightPanelHandler.dockPanel.widgetAdded.connect((_, widget) => this.fireDidAddWidget(widget));
this.rightPanelHandler.dockPanel.widgetRemoved.connect((_, widget) => this.fireDidRemoveWidget(widget));
this.layout = this.createLayout();
this.tracker.currentChanged.connect(this.onCurrentChanged, this);
this.tracker.activeChanged.connect(this.onActiveChanged, this);
}
/**
* Assemble the application shell layout. Override this method in order to change the arrangement
* of the main area and the side panels.
*/
protected createLayout(): Layout {
const bottomSplitLayout = this.createSplitLayout(
[this.mainPanel, this.bottomPanel],
[1, 0],
{ orientation: 'vertical', spacing: 0 }
);
const panelForBottomArea = new SplitPanel({ layout: bottomSplitLayout });
panelForBottomArea.id = 'theia-bottom-split-panel';
const leftRightSplitLayout = this.createSplitLayout(
[this.leftPanelHandler.container, panelForBottomArea, this.rightPanelHandler.container],
[0, 1, 0],
{ orientation: 'horizontal', spacing: 0 }
);
const panelForSideAreas = new SplitPanel({ layout: leftRightSplitLayout });
panelForSideAreas.id = 'theia-left-right-split-panel';
return this.createBoxLayout(
[this.topPanel, panelForSideAreas, this.statusBar],
[0, 1, 0],
{ direction: 'top-to-bottom', spacing: 0 }
);
}
}
自界说布局
以上介绍了ApplicationShell的组成和布局,那么咱们要扩展一个toolbar或许simulator也就简略了,只需重写ApplicationShell的createLayout办法,增加自己界说的视图,然后运用inversify重新绑定即可。其实官方供给了一个@theia/toolbar的模块,也是按上述的办法去重写的。效果如图:
代码如下:
@injectable()
export class ApplicationShellWithToolbarOverride extends ApplicationShell {
@inject(ToolbarPreferences) protected toolbarPreferences: ToolbarPreferences;
@inject(PreferenceService) protected readonly preferenceService: PreferenceService;
@inject(ToolbarFactory) protected readonly toolbarFactory: () => Toolbar;
protected toolbar: Toolbar;
@postConstruct()
protected override async init(): Promise<void> {
this.toolbar = this.toolbarFactory();
this.toolbar.id = 'main-toolbar';
super.init();
await this.toolbarPreferences.ready;
this.tryShowToolbar();
this.mainPanel.onDidToggleMaximized(() => {
this.tryShowToolbar();
});
this.bottomPanel.onDidToggleMaximized(() => {
this.tryShowToolbar();
});
this.preferenceService.onPreferenceChanged(event => {
if (event.preferenceName === TOOLBAR_ENABLE_PREFERENCE_ID) {
this.tryShowToolbar();
}
});
}
protected tryShowToolbar(): boolean {
const doShowToolbarFromPreference = this.toolbarPreferences[TOOLBAR_ENABLE_PREFERENCE_ID];
const isShellMaximized = this.mainPanel.hasClass(MAXIMIZED_CLASS) || this.bottomPanel.hasClass(MAXIMIZED_CLASS);
if (doShowToolbarFromPreference && !isShellMaximized) {
this.toolbar.show();
return true;
}
this.toolbar.hide();
return false;
}
protected override createLayout(): Layout {
const bottomSplitLayout = this.createSplitLayout(
[this.mainPanel, this.bottomPanel],
[1, 0],
{ orientation: 'vertical', spacing: 0 },
);
const panelForBottomArea = new SplitPanel({ layout: bottomSplitLayout });
panelForBottomArea.id = 'theia-bottom-split-panel';
const leftRightSplitLayout = this.createSplitLayout(
[this.leftPanelHandler.container, panelForBottomArea, this.rightPanelHandler.container],
[0, 1, 0],
{ orientation: 'horizontal', spacing: 0 },
);
const panelForSideAreas = new SplitPanel({ layout: leftRightSplitLayout });
panelForSideAreas.id = 'theia-left-right-split-panel';
return this.createBoxLayout(
[this.topPanel, this.toolbar, panelForSideAreas, this.statusBar],
[0, 0, 1, 0],
{ direction: 'top-to-bottom', spacing: 0 },
);
}
}
export const bindToolbarApplicationShell = (bind: interfaces.Bind, rebind: interfaces.Rebind, unbind: interfaces.Unbind): void => {
bind(ApplicationShellWithToolbarOverride).toSelf().inSingletonScope();
rebind(ApplicationShell).toService(ApplicationShellWithToolbarOverride);
};
界说了ApplicationShellWithToolbarOverride承继自ApplicationShell,然后创立toolbar,并在createLayout办法中将toolbar增加进去,最后将ApplicationShellWithToolbarOverride绑定到容器中,然后经过rebind替换掉ApplicationShell即可。
看完觉得对您有所帮助别忘记关注呦