@opensumi/ide-main-layout 2.21.13 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/accordion/accordion.service.js +1 -1
- package/lib/browser/accordion/accordion.service.js.map +1 -1
- package/lib/browser/accordion/styles.module.less +4 -2
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/layout.service.d.ts.map +1 -1
- package/lib/browser/layout.service.js +6 -6
- package/lib/browser/layout.service.js.map +1 -1
- package/lib/browser/main-layout.contribution.js +1 -1
- package/lib/browser/main-layout.contribution.js.map +1 -1
- package/lib/browser/quick-open-view.js +3 -2
- package/lib/browser/quick-open-view.js.map +1 -1
- package/lib/browser/tabbar/tabbar.service.js.map +1 -1
- package/lib/browser/tabbar-handler.js.map +1 -1
- package/lib/browser/views-registry.js +7 -7
- package/lib/browser/views-registry.js.map +1 -1
- package/package.json +10 -9
- package/src/browser/accordion/accordion.service.ts +635 -0
- package/src/browser/accordion/accordion.view.tsx +102 -0
- package/src/browser/accordion/section.view.tsx +181 -0
- package/src/browser/accordion/styles.module.less +214 -0
- package/src/browser/accordion/titlebar.view.tsx +17 -0
- package/src/browser/default-config.ts +35 -0
- package/src/browser/index.ts +40 -0
- package/src/browser/input/index.tsx +32 -0
- package/src/browser/layout.service.ts +515 -0
- package/src/browser/main-layout.contribution.ts +447 -0
- package/src/browser/quick-open-view.ts +138 -0
- package/src/browser/tabbar/bar.view.tsx +289 -0
- package/src/browser/tabbar/panel.view.tsx +190 -0
- package/src/browser/tabbar/renderer.view.tsx +133 -0
- package/src/browser/tabbar/styles.module.less +408 -0
- package/src/browser/tabbar/tabbar.service.ts +845 -0
- package/src/browser/tabbar-handler.ts +200 -0
- package/src/browser/views-registry.ts +157 -0
- package/src/browser/welcome.view.tsx +152 -0
- package/src/common/index.ts +2 -0
- package/src/common/main-layout.definition.ts +122 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Injectable, Autowired } from '@opensumi/di';
|
|
2
|
+
import { Event, Emitter, ILogger } from '@opensumi/ide-core-common';
|
|
3
|
+
|
|
4
|
+
import { IMainLayoutService } from '../common';
|
|
5
|
+
|
|
6
|
+
import { TabbarService } from './tabbar/tabbar.service';
|
|
7
|
+
|
|
8
|
+
@Injectable({ multiple: true })
|
|
9
|
+
export class TabBarHandler {
|
|
10
|
+
@Autowired(IMainLayoutService)
|
|
11
|
+
private layoutService!: IMainLayoutService;
|
|
12
|
+
|
|
13
|
+
@Autowired(ILogger)
|
|
14
|
+
private readonly logger: ILogger;
|
|
15
|
+
|
|
16
|
+
protected readonly onActivateEmitter = new Emitter<void>();
|
|
17
|
+
readonly onActivate: Event<void> = this.onActivateEmitter.event;
|
|
18
|
+
|
|
19
|
+
protected readonly onInActivateEmitter = new Emitter<void>();
|
|
20
|
+
readonly onInActivate: Event<void> = this.onInActivateEmitter.event;
|
|
21
|
+
|
|
22
|
+
// @deprecated
|
|
23
|
+
protected readonly onCollapseEmitter = new Emitter<void>();
|
|
24
|
+
protected readonly onCollapse: Event<void> = this.onCollapseEmitter.event;
|
|
25
|
+
|
|
26
|
+
public isVisible = false;
|
|
27
|
+
public accordionService = this.layoutService.getAccordionService(this.containerId);
|
|
28
|
+
|
|
29
|
+
constructor(public readonly containerId: string, private tabbarService: TabbarService) {
|
|
30
|
+
// 如果当前视图已经激活,则设置一些激活的标志
|
|
31
|
+
if (tabbarService.currentContainerId === this.containerId) {
|
|
32
|
+
this.onActivateEmitter.fire();
|
|
33
|
+
this.isVisible = true;
|
|
34
|
+
}
|
|
35
|
+
this.tabbarService.onCurrentChange((e) => {
|
|
36
|
+
if (e.currentId === this.containerId) {
|
|
37
|
+
this.onActivateEmitter.fire();
|
|
38
|
+
this.isVisible = true;
|
|
39
|
+
} else if (e.previousId === this.containerId) {
|
|
40
|
+
this.onInActivateEmitter.fire();
|
|
41
|
+
this.isVisible = false;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* dispose 整个视图面板
|
|
48
|
+
*/
|
|
49
|
+
dispose() {
|
|
50
|
+
// remove tab
|
|
51
|
+
this.tabbarService.containersMap.delete(this.containerId);
|
|
52
|
+
this.tabbarService.disposeContainer(this.containerId);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* dispose 子视图
|
|
56
|
+
*/
|
|
57
|
+
disposeView(viewId: string) {
|
|
58
|
+
this.layoutService.disposeViewComponent(viewId);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 激活该视图
|
|
62
|
+
*/
|
|
63
|
+
activate() {
|
|
64
|
+
this.tabbarService.currentContainerId = this.containerId;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 取消激活该视图
|
|
68
|
+
*/
|
|
69
|
+
deactivate() {
|
|
70
|
+
this.tabbarService.currentContainerId = '';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 当前视图激活状态
|
|
74
|
+
*/
|
|
75
|
+
isActivated() {
|
|
76
|
+
return this.tabbarService.currentContainerId === this.containerId;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 显示当前视图(区别于激活)
|
|
80
|
+
*/
|
|
81
|
+
show() {
|
|
82
|
+
this.tabbarService.getContainerState(this.containerId).hidden = false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 隐藏当前视图(区别于取消激活,整个视图将不展示在 tabbar 上)
|
|
86
|
+
*/
|
|
87
|
+
hide() {
|
|
88
|
+
this.tabbarService.getContainerState(this.containerId).hidden = true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 设置视图的顶部标题组件
|
|
92
|
+
*/
|
|
93
|
+
setTitleComponent(Fc: React.ComponentType, props?: object) {
|
|
94
|
+
const componentInfo = this.tabbarService.getContainer(this.containerId);
|
|
95
|
+
if (componentInfo) {
|
|
96
|
+
componentInfo.options!.titleProps = props;
|
|
97
|
+
componentInfo.options!.titleComponent = Fc;
|
|
98
|
+
this.tabbarService.forceUpdate++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 设置当前视图的展开尺寸,会强制展开面板
|
|
103
|
+
*/
|
|
104
|
+
setSize(size: number) {
|
|
105
|
+
this.layoutService.toggleSlot(
|
|
106
|
+
this.tabbarService.location,
|
|
107
|
+
true,
|
|
108
|
+
size + this.tabbarService.barSize /* border宽(高)度*/,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 设置视图tab的徽标
|
|
113
|
+
*/
|
|
114
|
+
setBadge(badge: string) {
|
|
115
|
+
this.tabbarService.getContainer(this.containerId)!.options!.badge = badge;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 获取视图tab的徽标
|
|
119
|
+
*/
|
|
120
|
+
getBadge() {
|
|
121
|
+
return this.tabbarService.getContainer(this.containerId)!.options!.badge;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 设置视图tab的图标
|
|
125
|
+
*/
|
|
126
|
+
setIconClass(iconClass: string) {
|
|
127
|
+
this.tabbarService.getContainer(this.containerId)!.options!.iconClass = iconClass;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 当前视图是否折叠(区别于激活,整个slot位置都会折叠)
|
|
131
|
+
*/
|
|
132
|
+
isCollapsed(viewId: string) {
|
|
133
|
+
return this.accordionService.getViewState(viewId).collapsed;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 折叠视图所在位置
|
|
137
|
+
*/
|
|
138
|
+
setCollapsed(viewId: string, collapsed: boolean) {
|
|
139
|
+
this.accordionService.toggleOpen(viewId, collapsed);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 切换子视图的折叠展开状态
|
|
143
|
+
*/
|
|
144
|
+
toggleViews(viewIds: string[], show: boolean) {
|
|
145
|
+
for (const viewId of viewIds) {
|
|
146
|
+
const viewState = this.accordionService.getViewState(viewId);
|
|
147
|
+
viewState.hidden = !show;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 更新子视图的标题
|
|
152
|
+
*/
|
|
153
|
+
updateViewTitle(viewId: string, title: string) {
|
|
154
|
+
const targetView = this.accordionService.views.find((view) => view.id === viewId);
|
|
155
|
+
if (targetView) {
|
|
156
|
+
targetView.name = title;
|
|
157
|
+
this.accordionService.updateViewTitle(viewId, title);
|
|
158
|
+
} else {
|
|
159
|
+
this.logger.error('没有找到目标视图,无法更新手风琴标题!');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 更新子视图的描述
|
|
165
|
+
*/
|
|
166
|
+
updateViewDescription(viewId: string, desciption: string) {
|
|
167
|
+
const targetView = this.accordionService.views.find((view) => view.id === viewId);
|
|
168
|
+
if (targetView) {
|
|
169
|
+
targetView.description = desciption;
|
|
170
|
+
this.accordionService.updateViewDesciption(viewId, desciption);
|
|
171
|
+
} else {
|
|
172
|
+
this.logger.error('没有找到目标视图,无法更新手风琴描述!');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 更新子视图的 message
|
|
178
|
+
*/
|
|
179
|
+
updateViewMessage(viewId: string, message: string) {
|
|
180
|
+
const targetView = this.accordionService.views.find((view) => view.id === viewId);
|
|
181
|
+
if (targetView) {
|
|
182
|
+
targetView.message = message;
|
|
183
|
+
this.accordionService.updateViewMessage(viewId, message);
|
|
184
|
+
} else {
|
|
185
|
+
this.logger.error('没有找到目标视图,无法更新手风琴 message!');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 更新视图的标题
|
|
190
|
+
*/
|
|
191
|
+
updateTitle(label: string) {
|
|
192
|
+
this.tabbarService.getContainer(this.containerId)!.options!.title = label;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 禁用侧边栏的resize功能
|
|
196
|
+
*/
|
|
197
|
+
setResizeLock(lock?: boolean) {
|
|
198
|
+
this.tabbarService.resizeHandle!.lockSize(lock);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Autowired, Injectable } from '@opensumi/di';
|
|
2
|
+
import { IDisposable, Event, Emitter, IContextKeyService, toDisposable } from '@opensumi/ide-core-browser';
|
|
3
|
+
import { SetMap } from '@opensumi/ide-core-common';
|
|
4
|
+
|
|
5
|
+
import { IViewContentDescriptor } from '..';
|
|
6
|
+
import { IViewsRegistry } from '../common';
|
|
7
|
+
|
|
8
|
+
export enum ViewContentGroups {
|
|
9
|
+
Open = '2_open',
|
|
10
|
+
Debug = '4_debug',
|
|
11
|
+
SCM = '5_scm',
|
|
12
|
+
More = '9_more',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface IItem {
|
|
16
|
+
readonly descriptor: IViewContentDescriptor;
|
|
17
|
+
visible: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number {
|
|
21
|
+
const aGroup = a.group ?? ViewContentGroups.More;
|
|
22
|
+
const bGroup = b.group ?? ViewContentGroups.More;
|
|
23
|
+
if (aGroup !== bGroup) {
|
|
24
|
+
return aGroup.localeCompare(bGroup);
|
|
25
|
+
}
|
|
26
|
+
return (a.order ?? 5) - (b.order ?? 5);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Injectable({ multiple: true })
|
|
30
|
+
export class ViewsController {
|
|
31
|
+
private _onDidChange = new Emitter<void>();
|
|
32
|
+
readonly onDidChange = this._onDidChange.event;
|
|
33
|
+
|
|
34
|
+
private defaultItem: IItem | undefined;
|
|
35
|
+
private items: IItem[] = [];
|
|
36
|
+
|
|
37
|
+
get contents(): IViewContentDescriptor[] {
|
|
38
|
+
const visibleItems = this.items.filter((v) => v.visible);
|
|
39
|
+
|
|
40
|
+
if (visibleItems.length === 0 && this.defaultItem) {
|
|
41
|
+
return [this.defaultItem.descriptor];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return visibleItems.map((v) => v.descriptor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Autowired(IContextKeyService)
|
|
48
|
+
contextKeyService: IContextKeyService;
|
|
49
|
+
|
|
50
|
+
@Autowired(IViewsRegistry)
|
|
51
|
+
viewsRegistry: IViewsRegistry;
|
|
52
|
+
|
|
53
|
+
private disposables: IDisposable[] = [];
|
|
54
|
+
|
|
55
|
+
constructor(private id: string) {
|
|
56
|
+
this.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
|
|
57
|
+
Event.filter(this.viewsRegistry.onDidChangeViewWelcomeContent, (id) => id === this.id)(
|
|
58
|
+
this.onDidChangeViewWelcomeContent,
|
|
59
|
+
this,
|
|
60
|
+
this.disposables,
|
|
61
|
+
);
|
|
62
|
+
this.onDidChangeViewWelcomeContent();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private onDidChangeViewWelcomeContent(): void {
|
|
66
|
+
const descriptors = this.viewsRegistry.getViewWelcomeContent(this.id);
|
|
67
|
+
|
|
68
|
+
this.items = [];
|
|
69
|
+
|
|
70
|
+
for (const descriptor of descriptors) {
|
|
71
|
+
if (descriptor.when === 'default') {
|
|
72
|
+
this.defaultItem = { descriptor, visible: true };
|
|
73
|
+
} else {
|
|
74
|
+
const visible = descriptor.when ? this.contextKeyService.match(descriptor.when) : true;
|
|
75
|
+
this.items.push({ descriptor, visible });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this._onDidChange.fire();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private onDidChangeContext(): void {
|
|
83
|
+
let didChange = false;
|
|
84
|
+
|
|
85
|
+
for (const item of this.items) {
|
|
86
|
+
if (!item.descriptor.when || item.descriptor.when === 'default') {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const visible = this.contextKeyService.match(item.descriptor.when);
|
|
91
|
+
|
|
92
|
+
if (item.visible === visible) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
item.visible = visible;
|
|
97
|
+
didChange = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (didChange) {
|
|
101
|
+
this._onDidChange.fire();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
dispose(): void {
|
|
106
|
+
this.disposables.forEach((item) => item.dispose());
|
|
107
|
+
this.disposables = [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Injectable()
|
|
112
|
+
export class ViewsRegistry implements IViewsRegistry {
|
|
113
|
+
private viewWelcomeContent = new SetMap<string, IViewContentDescriptor>();
|
|
114
|
+
|
|
115
|
+
private readonly _onDidChangeViewWelcomeContent = new Emitter<string>();
|
|
116
|
+
readonly onDidChangeViewWelcomeContent = this._onDidChangeViewWelcomeContent.event;
|
|
117
|
+
|
|
118
|
+
registerViewWelcomeContent(id: string, descriptor: IViewContentDescriptor): IDisposable {
|
|
119
|
+
this.viewWelcomeContent.add(id, descriptor);
|
|
120
|
+
this._onDidChangeViewWelcomeContent.fire(id);
|
|
121
|
+
return {
|
|
122
|
+
dispose: () => {
|
|
123
|
+
this.viewWelcomeContent.delete(id, descriptor);
|
|
124
|
+
this._onDidChangeViewWelcomeContent.fire(id);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
registerViewWelcomeContent2<TKey>(
|
|
130
|
+
id: string,
|
|
131
|
+
viewContentMap: Map<TKey, IViewContentDescriptor>,
|
|
132
|
+
): Map<TKey, IDisposable> {
|
|
133
|
+
const disposables = new Map<TKey, IDisposable>();
|
|
134
|
+
|
|
135
|
+
for (const [key, content] of viewContentMap) {
|
|
136
|
+
this.viewWelcomeContent.add(id, content);
|
|
137
|
+
|
|
138
|
+
disposables.set(
|
|
139
|
+
key,
|
|
140
|
+
toDisposable(() => {
|
|
141
|
+
this.viewWelcomeContent.delete(id, content);
|
|
142
|
+
this._onDidChangeViewWelcomeContent.fire(id);
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
this._onDidChangeViewWelcomeContent.fire(id);
|
|
147
|
+
|
|
148
|
+
return disposables;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getViewWelcomeContent(id: string) {
|
|
152
|
+
const result: IViewContentDescriptor[] = [];
|
|
153
|
+
this.viewWelcomeContent.forEach(id, (descriptor) => result.push(descriptor));
|
|
154
|
+
result.sort(compareViewContentDescriptors);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import clsx from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '@opensumi/ide-components/lib/button';
|
|
5
|
+
import { getExternalIcon, IOpenerService, useInjectable } from '@opensumi/ide-core-browser';
|
|
6
|
+
import { IContextKeyService } from '@opensumi/ide-core-browser';
|
|
7
|
+
import { parseLinkedText } from '@opensumi/ide-core-common';
|
|
8
|
+
|
|
9
|
+
import { IViewContentDescriptor } from '../common';
|
|
10
|
+
|
|
11
|
+
import styles from './accordion/styles.module.less';
|
|
12
|
+
import { ViewsController } from './views-registry';
|
|
13
|
+
|
|
14
|
+
export namespace CSSIcon {
|
|
15
|
+
export const iconNameSegment = '[A-Za-z0-9]+';
|
|
16
|
+
export const iconNameExpression = '[A-Za-z0-9\\-]+';
|
|
17
|
+
export const iconModifierExpression = '~[A-Za-z]+';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const labelWithIconsRegex = new RegExp(
|
|
21
|
+
`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`,
|
|
22
|
+
'g',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export function renderLabelWithIcons(text: string): Array<React.ReactElement | string> {
|
|
26
|
+
const elements = new Array<React.ReactElement | string>();
|
|
27
|
+
let match: RegExpMatchArray | null;
|
|
28
|
+
|
|
29
|
+
let textStart = 0;
|
|
30
|
+
let textStop = 0;
|
|
31
|
+
while ((match = labelWithIconsRegex.exec(text)) !== null) {
|
|
32
|
+
textStop = match.index || 0;
|
|
33
|
+
elements.push(text.substring(textStart, textStop));
|
|
34
|
+
textStart = (match.index || 0) + match[0].length;
|
|
35
|
+
|
|
36
|
+
const [, escaped, codicon] = match;
|
|
37
|
+
elements.push(escaped ? `$(${codicon})` : <span className={getExternalIcon(codicon)}></span>);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (textStart < text.length) {
|
|
41
|
+
elements.push(text.substring(textStart));
|
|
42
|
+
}
|
|
43
|
+
return elements;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const WelcomeContent = (props: { contents: IViewContentDescriptor[] }) => {
|
|
47
|
+
const { contents } = props;
|
|
48
|
+
const [disables, setDisables] = React.useState<(boolean | null)[]>(
|
|
49
|
+
contents.map((item) => (item.precondition ? false : null)),
|
|
50
|
+
);
|
|
51
|
+
const contextKeyService: IContextKeyService = useInjectable(IContextKeyService);
|
|
52
|
+
const openerService: IOpenerService = useInjectable(IOpenerService);
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const conditionKeys = contents.map((item) => {
|
|
56
|
+
if (item.precondition) {
|
|
57
|
+
const keys = new Set();
|
|
58
|
+
item.precondition.keys().forEach((key) => keys.add(key));
|
|
59
|
+
return keys;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
});
|
|
63
|
+
const disposable = contextKeyService.onDidChangeContext((e) => {
|
|
64
|
+
conditionKeys.forEach((keysOrNull, index) => {
|
|
65
|
+
if (keysOrNull && e.payload.affectsSome(keysOrNull)) {
|
|
66
|
+
setDisables(
|
|
67
|
+
disables.map((item, idx) => (idx === index ? contextKeyService.match(contents[index].precondition) : item)),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return () => disposable.dispose();
|
|
73
|
+
}, [contents]);
|
|
74
|
+
|
|
75
|
+
if (contents.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
{contents.map(({ content, precondition }, index) => {
|
|
81
|
+
const lines = content.split('\n');
|
|
82
|
+
const lineElements: React.ReactElement[] = [];
|
|
83
|
+
for (let line of lines) {
|
|
84
|
+
line = line.trim();
|
|
85
|
+
|
|
86
|
+
if (!line) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const linkedText = parseLinkedText(line);
|
|
91
|
+
if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {
|
|
92
|
+
const node = linkedText.nodes[0];
|
|
93
|
+
lineElements.push(
|
|
94
|
+
<div key={lineElements.length} title={node.title} className='button-container'>
|
|
95
|
+
<Button disabled={disables[index] === false} onClick={() => openerService.open(node.href)}>
|
|
96
|
+
{renderLabelWithIcons(node.label)}
|
|
97
|
+
</Button>
|
|
98
|
+
</div>,
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
const textNodes = linkedText.nodes.map((node, idx) => {
|
|
102
|
+
if (typeof node === 'string') {
|
|
103
|
+
return node;
|
|
104
|
+
} else {
|
|
105
|
+
return (
|
|
106
|
+
<a
|
|
107
|
+
key={idx}
|
|
108
|
+
className={clsx({ disabled: node.href.startsWith('command:') && disables[index] === false })}
|
|
109
|
+
title={node.title}
|
|
110
|
+
onClick={() => openerService.open(node.href)}
|
|
111
|
+
>
|
|
112
|
+
{node.label}
|
|
113
|
+
</a>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
lineElements.push(<p key={lineElements.length}>{textNodes}</p>);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return <React.Fragment key={index}>{lineElements}</React.Fragment>;
|
|
121
|
+
})}
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
interface WelcomeViewProps {
|
|
127
|
+
viewId: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Welcome view 不关心 viewState 变化
|
|
132
|
+
*/
|
|
133
|
+
function isWelcomeViewViewIdEqual(prevProps: WelcomeViewProps, nextProps: WelcomeViewProps) {
|
|
134
|
+
return prevProps.viewId === nextProps.viewId;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const WelcomeView: React.FC<WelcomeViewProps> = React.memo((props) => {
|
|
138
|
+
const viewsController: ViewsController = useInjectable(ViewsController, [props.viewId]);
|
|
139
|
+
const [contents, setContents] = React.useState<IViewContentDescriptor[]>(viewsController.contents);
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
viewsController.onDidChange(() => {
|
|
142
|
+
const newContents = viewsController.contents;
|
|
143
|
+
setContents(newContents);
|
|
144
|
+
});
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
147
|
+
return contents.length ? (
|
|
148
|
+
<div className={styles.welcome}>
|
|
149
|
+
<WelcomeContent contents={contents} />
|
|
150
|
+
</div>
|
|
151
|
+
) : null;
|
|
152
|
+
}, isWelcomeViewViewIdEqual);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BasicEvent, IDisposable, SlotLocation } from '@opensumi/ide-core-browser';
|
|
2
|
+
import { ViewContainerOptions, View, SideStateManager } from '@opensumi/ide-core-browser/lib/layout';
|
|
3
|
+
import { IContextMenu } from '@opensumi/ide-core-browser/lib/menu/next';
|
|
4
|
+
import { Deferred, Event } from '@opensumi/ide-core-common';
|
|
5
|
+
import { IContextKeyExpression } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line import/no-restricted-paths
|
|
8
|
+
import type { AccordionService } from '../browser/accordion/accordion.service';
|
|
9
|
+
// eslint-disable-next-line import/no-restricted-paths
|
|
10
|
+
import type { TabBarHandler } from '../browser/tabbar-handler';
|
|
11
|
+
// eslint-disable-next-line import/no-restricted-paths
|
|
12
|
+
import type { TabbarService } from '../browser/tabbar/tabbar.service';
|
|
13
|
+
|
|
14
|
+
export interface ComponentCollection {
|
|
15
|
+
views?: View[];
|
|
16
|
+
options: ViewContainerOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ViewComponentOptions {
|
|
20
|
+
isReplace?: boolean;
|
|
21
|
+
fromExtension?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const IMainLayoutService = Symbol('IMainLayoutService');
|
|
25
|
+
export interface IMainLayoutService {
|
|
26
|
+
viewReady: Deferred<void>;
|
|
27
|
+
|
|
28
|
+
didMount(): void;
|
|
29
|
+
// 切换tabbar位置的slot,支持left、right、bottom
|
|
30
|
+
toggleSlot(location: SlotLocation, show?: boolean, size?: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* 获取注册到tabbar位置视图的handler,封装了常用的layout操作
|
|
33
|
+
* 请在onRendered事件触发后或onDidRender contribution内获取handle,否则获取到为空
|
|
34
|
+
* @param handlerId container或view id
|
|
35
|
+
*/
|
|
36
|
+
getTabbarHandler(handlerId: string): TabBarHandler | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* 注册单个或多个视图到tabbar位置
|
|
39
|
+
* @param views 使用手风琴能力时传入的多个子视图
|
|
40
|
+
* @param options container相关选项
|
|
41
|
+
* @param side 注册的位置,支持left、right、bottom
|
|
42
|
+
*/
|
|
43
|
+
collectTabbarComponent(views: View[], options: ViewContainerOptions, side: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* 向侧边栏container内附加新的子视图
|
|
46
|
+
* @param view 子视图信息
|
|
47
|
+
* @param containerId 子视图需要附加的容器id
|
|
48
|
+
* @param props 初始prop
|
|
49
|
+
*/
|
|
50
|
+
collectViewComponent(view: View, containerId: string, props?: any, options?: ViewComponentOptions): string;
|
|
51
|
+
/**
|
|
52
|
+
* 替换一个已注册的视图
|
|
53
|
+
* @param view 子视图信息
|
|
54
|
+
* @param props 初始prop
|
|
55
|
+
*/
|
|
56
|
+
replaceViewComponent(view: View, props?: any): void;
|
|
57
|
+
/**
|
|
58
|
+
* 从手风琴销毁一个子视图
|
|
59
|
+
* @param viewId 子视图ID
|
|
60
|
+
*/
|
|
61
|
+
disposeViewComponent(viewId: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* 销毁一个容器视图
|
|
64
|
+
* @param containerId 容器视图ID
|
|
65
|
+
*/
|
|
66
|
+
disposeContainer(containerId: string): void;
|
|
67
|
+
expandBottom(expand: boolean): void;
|
|
68
|
+
bottomExpanded: boolean;
|
|
69
|
+
// @deprecated 提供小程序使用的额外位置控制
|
|
70
|
+
setFloatSize(size: number): void;
|
|
71
|
+
// force reveal a view ignoring its when clause
|
|
72
|
+
revealView(viewId: string): void;
|
|
73
|
+
getTabbarService(location: string): TabbarService;
|
|
74
|
+
getAccordionService(containerId: string, noRestore?: boolean): AccordionService;
|
|
75
|
+
getViewAccordionService(viewId: string): AccordionService | undefined;
|
|
76
|
+
// 某一位置是否可见
|
|
77
|
+
isVisible(location: string): boolean;
|
|
78
|
+
isViewVisible(viewId: string): boolean;
|
|
79
|
+
getExtraMenu(): IContextMenu;
|
|
80
|
+
getAllAccordionService(): Map<string, AccordionService>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const MainLayoutContribution = Symbol('MainLayoutContribution');
|
|
84
|
+
|
|
85
|
+
export interface MainLayoutContribution {
|
|
86
|
+
// 将LayoutConfig渲染到各Slot后调用
|
|
87
|
+
onDidRender?(): void;
|
|
88
|
+
|
|
89
|
+
provideDefaultState?(): SideStateManager;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 当有新的TabBar被注册时发送的新事件
|
|
94
|
+
*/
|
|
95
|
+
export class TabBarRegistrationEvent extends BasicEvent<{ tabBarId: string }> {}
|
|
96
|
+
|
|
97
|
+
export const IViewsRegistry = Symbol('IViewsRegistry');
|
|
98
|
+
|
|
99
|
+
export interface IViewsRegistry {
|
|
100
|
+
readonly onDidChangeViewWelcomeContent: Event<string>;
|
|
101
|
+
registerViewWelcomeContent(id: string, descriptor: IViewContentDescriptor): IDisposable;
|
|
102
|
+
registerViewWelcomeContent2<TKey>(
|
|
103
|
+
id: string,
|
|
104
|
+
viewContentMap: Map<TKey, IViewContentDescriptor>,
|
|
105
|
+
): Map<TKey, IDisposable>;
|
|
106
|
+
getViewWelcomeContent(id: string): IViewContentDescriptor[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface IViewContentDescriptor {
|
|
110
|
+
readonly content: string;
|
|
111
|
+
readonly when?: IContextKeyExpression | 'default';
|
|
112
|
+
readonly group?: string;
|
|
113
|
+
readonly order?: number;
|
|
114
|
+
readonly precondition?: IContextKeyExpression | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class ViewCollapseChangedEvent extends BasicEvent<{
|
|
118
|
+
viewId: string;
|
|
119
|
+
collapsed: boolean;
|
|
120
|
+
}> {}
|
|
121
|
+
|
|
122
|
+
export const SUPPORT_ACCORDION_LOCATION = new Set([SlotLocation.left, SlotLocation.right]);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './common';
|