@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.
Files changed (38) hide show
  1. package/lib/browser/accordion/accordion.service.js +1 -1
  2. package/lib/browser/accordion/accordion.service.js.map +1 -1
  3. package/lib/browser/accordion/styles.module.less +4 -2
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/layout.service.d.ts.map +1 -1
  6. package/lib/browser/layout.service.js +6 -6
  7. package/lib/browser/layout.service.js.map +1 -1
  8. package/lib/browser/main-layout.contribution.js +1 -1
  9. package/lib/browser/main-layout.contribution.js.map +1 -1
  10. package/lib/browser/quick-open-view.js +3 -2
  11. package/lib/browser/quick-open-view.js.map +1 -1
  12. package/lib/browser/tabbar/tabbar.service.js.map +1 -1
  13. package/lib/browser/tabbar-handler.js.map +1 -1
  14. package/lib/browser/views-registry.js +7 -7
  15. package/lib/browser/views-registry.js.map +1 -1
  16. package/package.json +10 -9
  17. package/src/browser/accordion/accordion.service.ts +635 -0
  18. package/src/browser/accordion/accordion.view.tsx +102 -0
  19. package/src/browser/accordion/section.view.tsx +181 -0
  20. package/src/browser/accordion/styles.module.less +214 -0
  21. package/src/browser/accordion/titlebar.view.tsx +17 -0
  22. package/src/browser/default-config.ts +35 -0
  23. package/src/browser/index.ts +40 -0
  24. package/src/browser/input/index.tsx +32 -0
  25. package/src/browser/layout.service.ts +515 -0
  26. package/src/browser/main-layout.contribution.ts +447 -0
  27. package/src/browser/quick-open-view.ts +138 -0
  28. package/src/browser/tabbar/bar.view.tsx +289 -0
  29. package/src/browser/tabbar/panel.view.tsx +190 -0
  30. package/src/browser/tabbar/renderer.view.tsx +133 -0
  31. package/src/browser/tabbar/styles.module.less +408 -0
  32. package/src/browser/tabbar/tabbar.service.ts +845 -0
  33. package/src/browser/tabbar-handler.ts +200 -0
  34. package/src/browser/views-registry.ts +157 -0
  35. package/src/browser/welcome.view.tsx +152 -0
  36. package/src/common/index.ts +2 -0
  37. package/src/common/main-layout.definition.ts +122 -0
  38. package/src/index.ts +1 -0
@@ -0,0 +1,515 @@
1
+ import debounce from 'lodash/debounce';
2
+
3
+ import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
4
+ import {
5
+ WithEventBus,
6
+ IDisposable,
7
+ View,
8
+ ViewContainerOptions,
9
+ ContributionProvider,
10
+ SlotLocation,
11
+ IContextKeyService,
12
+ ExtensionActivateEvent,
13
+ AppConfig,
14
+ ComponentRegistry,
15
+ ILogger,
16
+ CommandRegistry,
17
+ CommandService,
18
+ OnEvent,
19
+ slotRendererRegistry,
20
+ } from '@opensumi/ide-core-browser';
21
+ import { LayoutState, LAYOUT_STATE } from '@opensumi/ide-core-browser/lib/layout/layout-state';
22
+ import {
23
+ IMenuRegistry,
24
+ AbstractContextMenuService,
25
+ MenuId,
26
+ AbstractMenuService,
27
+ IContextMenu,
28
+ } from '@opensumi/ide-core-browser/lib/menu/next';
29
+ import { Deferred, getDebugLogger } from '@opensumi/ide-core-common';
30
+ import { ThemeChangedEvent } from '@opensumi/ide-theme';
31
+
32
+ import {
33
+ MainLayoutContribution,
34
+ IMainLayoutService,
35
+ ViewComponentOptions,
36
+ SUPPORT_ACCORDION_LOCATION,
37
+ } from '../common';
38
+
39
+ import { AccordionService } from './accordion/accordion.service';
40
+ import { TabBarHandler } from './tabbar-handler';
41
+ import { TabbarService } from './tabbar/tabbar.service';
42
+
43
+ @Injectable()
44
+ export class LayoutService extends WithEventBus implements IMainLayoutService {
45
+ @Autowired(INJECTOR_TOKEN)
46
+ private injector: Injector;
47
+
48
+ @Autowired(MainLayoutContribution)
49
+ private readonly contributions: ContributionProvider<MainLayoutContribution>;
50
+
51
+ @Autowired(IMenuRegistry)
52
+ menus: IMenuRegistry;
53
+
54
+ @Autowired(CommandRegistry)
55
+ private readonly commandRegistry: CommandRegistry;
56
+
57
+ @Autowired(CommandService)
58
+ private readonly commandService: CommandService;
59
+
60
+ @Autowired()
61
+ private layoutState: LayoutState;
62
+
63
+ @Autowired(AppConfig)
64
+ private appConfig: AppConfig;
65
+
66
+ @Autowired(IContextKeyService)
67
+ private contextKeyService: IContextKeyService;
68
+
69
+ @Autowired(ComponentRegistry)
70
+ private componentRegistry: ComponentRegistry;
71
+
72
+ @Autowired(ILogger)
73
+ private logger: ILogger;
74
+
75
+ private handleMap: Map<string, TabBarHandler> = new Map();
76
+
77
+ private tabbarServices: Map<string, TabbarService> = new Map();
78
+
79
+ private accordionServices: Map<string, AccordionService> = new Map();
80
+
81
+ private pendingViewsMap: Map<string, { view: View; props?: any }[]> = new Map();
82
+
83
+ private viewToContainerMap: Map<string, string> = new Map();
84
+
85
+ private disposableMap: Map<string, IDisposable> = new Map();
86
+
87
+ private state: {
88
+ [location: string]: {
89
+ currentId?: string;
90
+ size?: number;
91
+ };
92
+ } = {};
93
+
94
+ private customViews = new Map<string, View>();
95
+
96
+ private debug = getDebugLogger();
97
+
98
+ @Autowired(AbstractMenuService)
99
+ protected menuService: AbstractMenuService;
100
+
101
+ @Autowired(AbstractContextMenuService)
102
+ protected contextmenuService: AbstractContextMenuService;
103
+
104
+ public viewReady: Deferred<void> = new Deferred();
105
+
106
+ constructor() {
107
+ super();
108
+ }
109
+
110
+ didMount() {
111
+ for (const [containerId, views] of this.pendingViewsMap.entries()) {
112
+ views.forEach(({ view, props }) => {
113
+ this.collectViewComponent(view, containerId, props);
114
+ });
115
+ }
116
+ for (const contribution of this.contributions.getContributions()) {
117
+ if (contribution.onDidRender) {
118
+ contribution.onDidRender();
119
+ }
120
+ }
121
+ const list: Array<Promise<void>> = [];
122
+ // 这里保证的 viewReady 并不是真实的 viewReady,只是保证在此刻之前注册进来的 Tabbar Ready 了
123
+ // 仅确保 tabbar 视图加载完毕
124
+ this.tabbarServices.forEach((service) => {
125
+ if (slotRendererRegistry.isTabbar(service.location)) {
126
+ list.push(service.viewReady.promise);
127
+ }
128
+ });
129
+ Promise.all(list).then(() => {
130
+ this.viewReady.resolve();
131
+ });
132
+ }
133
+
134
+ setFloatSize(size: number) {}
135
+
136
+ storeState(service: TabbarService, currentId: string) {
137
+ this.state[service.location] = {
138
+ currentId,
139
+ size: service.prevSize,
140
+ };
141
+ this.layoutState.setState(LAYOUT_STATE.MAIN, this.state);
142
+ }
143
+
144
+ @OnEvent(ThemeChangedEvent)
145
+ onThemeChange(e: ThemeChangedEvent) {
146
+ const theme = e.payload.theme;
147
+ localStorage.setItem(
148
+ 'theme',
149
+ JSON.stringify({
150
+ menuBarBackground: theme.getColor('kt.menubar.background')?.toString(),
151
+ sideBarBackground: theme.getColor('sideBar.background')?.toString(),
152
+ editorBackground: theme.getColor('editor.background')?.toString(),
153
+ panelBackground: theme.getColor('panel.background')?.toString(),
154
+ statusBarBackground: theme.getColor('statusBar.background')?.toString(),
155
+ }),
156
+ );
157
+ }
158
+
159
+ restoreTabbarService = async (service: TabbarService) => {
160
+ await service.viewReady.promise;
161
+
162
+ this.state = this.layoutState.getState(LAYOUT_STATE.MAIN, {
163
+ [SlotLocation.left]: {
164
+ currentId: undefined,
165
+ size: undefined,
166
+ },
167
+ [SlotLocation.right]: {
168
+ // 依照下面的恢复逻辑,这里设置为 `''` 时,就不会恢复右侧的 TabBar 的状态(即选中相应的 viewContainer)
169
+ currentId: '',
170
+ size: undefined,
171
+ },
172
+ [SlotLocation.bottom]: {
173
+ currentId: undefined,
174
+ size: undefined,
175
+ },
176
+ });
177
+
178
+ const { currentId, size } = this.state[service.location] || {};
179
+ service.prevSize = size;
180
+ let defaultContainer = service.visibleContainers[0] && service.visibleContainers[0].options!.containerId;
181
+ const defaultPanels = this.appConfig.defaultPanels;
182
+ const restorePanel = defaultPanels && defaultPanels[service.location];
183
+ if (defaultPanels && restorePanel !== undefined) {
184
+ if (restorePanel) {
185
+ if (service.containersMap.has(restorePanel)) {
186
+ defaultContainer = restorePanel;
187
+ } else {
188
+ const componentInfo = this.componentRegistry.getComponentRegistryInfo(restorePanel);
189
+ if (
190
+ componentInfo &&
191
+ this.appConfig.layoutConfig[service.location]?.modules &&
192
+ ~this.appConfig.layoutConfig[service.location].modules.indexOf(restorePanel)
193
+ ) {
194
+ defaultContainer = componentInfo.options!.containerId;
195
+ } else {
196
+ this.logger.warn(`[defaultPanels] No \`${restorePanel}\` view found!`);
197
+ }
198
+ }
199
+ } else {
200
+ defaultContainer = '';
201
+ }
202
+ }
203
+ if (currentId === undefined) {
204
+ service.currentContainerId = defaultContainer;
205
+ } else {
206
+ service.currentContainerId = currentId
207
+ ? service.containersMap.has(currentId)
208
+ ? currentId
209
+ : defaultContainer
210
+ : '';
211
+ }
212
+ };
213
+
214
+ isVisible(location: string) {
215
+ const tabbarService = this.getTabbarService(location);
216
+ return !!tabbarService.currentContainerId;
217
+ }
218
+
219
+ isViewVisible(viewId: string): boolean {
220
+ const tabbarHandler = this.getTabbarHandler(viewId);
221
+ if (!tabbarHandler || !tabbarHandler.isActivated()) {
222
+ return false;
223
+ }
224
+ const viewState = tabbarHandler.accordionService.getViewState(viewId);
225
+ return !viewState.collapsed && !viewState.hidden;
226
+ }
227
+
228
+ toggleSlot(location: string, show?: boolean | undefined, size?: number | undefined): void {
229
+ const tabbarService = this.getTabbarService(location);
230
+ if (!tabbarService) {
231
+ this.debug.error(`Unable to switch panels because no TabbarService corresponding to \`${location}\` was found.`);
232
+ return;
233
+ }
234
+ if (show === true) {
235
+ tabbarService.currentContainerId =
236
+ tabbarService.currentContainerId ||
237
+ tabbarService.previousContainerId ||
238
+ tabbarService.containersMap.keys().next().value;
239
+ } else if (show === false) {
240
+ tabbarService.currentContainerId = '';
241
+ } else {
242
+ tabbarService.currentContainerId = tabbarService.currentContainerId
243
+ ? ''
244
+ : tabbarService.previousContainerId || tabbarService.containersMap.keys().next().value;
245
+ }
246
+ if (tabbarService.currentContainerId && size) {
247
+ tabbarService.resizeHandle?.setSize(size);
248
+ }
249
+ }
250
+
251
+ getTabbarService(location: string) {
252
+ const service = this.tabbarServices.get(location) || this.injector.get(TabbarService, [location]);
253
+ if (!this.tabbarServices.get(location)) {
254
+ service.onCurrentChange(({ currentId }) => {
255
+ this.storeState(service, currentId);
256
+ // onView 也支持监听 containerId
257
+ this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: currentId }));
258
+ if (currentId && SUPPORT_ACCORDION_LOCATION.has(service.location)) {
259
+ const accordionService = this.getAccordionService(currentId);
260
+ accordionService.tryUpdateResize();
261
+ accordionService.expandedViews.forEach((view) => {
262
+ this.eventBus.fire(new ExtensionActivateEvent({ topic: 'onView', data: view.id }));
263
+ });
264
+ }
265
+ });
266
+ service.viewReady.promise
267
+ .then(() => service.restoreState())
268
+ .then(() => this.restoreTabbarService(service))
269
+ .catch((err) => {
270
+ this.logger.error(`[TabbarService:${location}] restore state error`, err);
271
+ });
272
+ service.onSizeChange(() => debounce(() => this.storeState(service, service.currentContainerId), 200)());
273
+ this.tabbarServices.set(location, service);
274
+ }
275
+ return service;
276
+ }
277
+
278
+ getAllAccordionService() {
279
+ return this.accordionServices;
280
+ }
281
+
282
+ getAccordionService(containerId: string, noRestore?: boolean) {
283
+ let service = this.accordionServices.get(containerId);
284
+ if (!service) {
285
+ service = this.injector.get(AccordionService, [containerId, noRestore]);
286
+ this.accordionServices.set(containerId, service);
287
+ }
288
+ return service;
289
+ }
290
+
291
+ getTabbarHandler(viewOrContainerId: string): TabBarHandler | undefined {
292
+ let handler = this.doGetTabbarHandler(viewOrContainerId);
293
+ if (!handler) {
294
+ const containerId = this.viewToContainerMap.get(viewOrContainerId);
295
+ if (!containerId) {
296
+ this.debug.warn(`${viewOrContainerId} view tabbar not found.`);
297
+ } else {
298
+ handler = this.doGetTabbarHandler(containerId || '');
299
+ }
300
+ }
301
+ return handler;
302
+ }
303
+
304
+ getExtraMenu(): IContextMenu {
305
+ return this.contextmenuService.createMenu({
306
+ id: MenuId.ActivityBarExtra,
307
+ });
308
+ }
309
+
310
+ protected doGetTabbarHandler(containerId: string) {
311
+ let activityHandler = this.handleMap.get(containerId);
312
+ if (!activityHandler) {
313
+ let location: string | undefined;
314
+ for (const service of this.tabbarServices.values()) {
315
+ if (service.getContainer(containerId)) {
316
+ location = service.location;
317
+ break;
318
+ }
319
+ }
320
+ if (location) {
321
+ activityHandler = this.injector.get(TabBarHandler, [containerId, this.getTabbarService(location)]);
322
+ this.handleMap.set(containerId, activityHandler);
323
+ }
324
+ }
325
+ return activityHandler;
326
+ }
327
+
328
+ private holdTabbarComponent = new Map<string, { views: View[]; options: ViewContainerOptions; side: string }>();
329
+
330
+ collectTabbarComponent(views: View[], options: ViewContainerOptions, side: string, Fc?: any): string {
331
+ if (Fc) {
332
+ this.debug.warn('collectTabbarComponent api warning: Please move react component into options.component!');
333
+ }
334
+ if (options.hideIfEmpty && !views.length && !options.component) {
335
+ this.holdTabbarComponent.set(options.containerId, { views, options, side });
336
+ if (this.tabbarUpdateSet.has(options.containerId)) {
337
+ this.tryUpdateTabbar(options.containerId);
338
+ }
339
+ const service = this.getAccordionService(options.containerId);
340
+ // 如果 append view 时尝试注册 holdTabbarComponent
341
+ service.onBeforeAppendViewEvent(() => {
342
+ this.tryUpdateTabbar(options.containerId);
343
+ });
344
+ service.onAfterDisposeViewEvent(() => {
345
+ // 如果没有其他 view ,则 remove 掉 container
346
+ if (service.views.length === 0) {
347
+ this.disposeContainer(options.containerId);
348
+ // 重新注册到 holdTabbarComponent ,以便再次 append 时能注册传上去
349
+ this.holdTabbarComponent.set(options.containerId, { views, options, side });
350
+ }
351
+ });
352
+ return options.containerId;
353
+ }
354
+ const tabbarService = this.getTabbarService(side);
355
+ tabbarService.registerContainer(options.containerId, { views, options });
356
+ views.forEach((view) => {
357
+ this.viewToContainerMap.set(view.id, options.containerId);
358
+ });
359
+ return options.containerId;
360
+ }
361
+
362
+ getViewAccordionService(viewId: string) {
363
+ const containerId = this.viewToContainerMap.get(viewId);
364
+ if (!containerId) {
365
+ return;
366
+ }
367
+
368
+ return this.getAccordionService(containerId);
369
+ }
370
+
371
+ collectViewComponent(view: View, containerId: string, props: any = {}, options?: ViewComponentOptions): string {
372
+ this.customViews.set(view.id, view);
373
+ this.viewToContainerMap.set(view.id, containerId);
374
+ const accordionService: AccordionService = this.getAccordionService(containerId);
375
+ if (props) {
376
+ view.initialProps = props;
377
+ }
378
+ accordionService.appendView(view, options?.isReplace);
379
+
380
+ // 如果之前没有views信息,且为hideIfEmpty类型视图则需要刷新
381
+ if (accordionService.views.length === 1) {
382
+ this.tabbarUpdateSet.add(containerId);
383
+ this.tryUpdateTabbar(containerId);
384
+ }
385
+
386
+ if (options?.fromExtension) {
387
+ this.disposableMap.set(
388
+ view.id,
389
+ this.commandRegistry.registerCommand(
390
+ {
391
+ id: `${view.id}.focus`,
392
+ },
393
+ {
394
+ execute: async () => {
395
+ await this.ensureViewReady(view.id);
396
+ // TODO: 目前 view 没有 focus 状态,先跳转到对应的 container 上
397
+ return this.commandService.executeCommand(`workbench.view.extension.${containerId}`, { forceShow: true });
398
+ },
399
+ },
400
+ ),
401
+ );
402
+ }
403
+
404
+ return containerId;
405
+ }
406
+
407
+ private ensureViewReady(viewId: string) {
408
+ const containerId = this.viewToContainerMap.get(viewId)!;
409
+ const viewReady = new Deferred<void>();
410
+ const accordionService = this.getAccordionService(containerId);
411
+ if (!accordionService.visibleViews.find((view) => view.id === viewId)) {
412
+ accordionService.onAfterAppendViewEvent((id) => {
413
+ if (id === viewId) {
414
+ viewReady.resolve();
415
+ }
416
+ });
417
+ } else {
418
+ viewReady.resolve();
419
+ }
420
+ return viewReady.promise;
421
+ }
422
+
423
+ // 时序保证用,view先注册,container后注册同样需要触发更新
424
+ private tabbarUpdateSet: Set<string> = new Set();
425
+
426
+ // 由于注册container和view的时序不能保障,注册时需要互相触发
427
+ private tryUpdateTabbar(containerId: string) {
428
+ const holdInfo = this.holdTabbarComponent.get(containerId);
429
+ if (holdInfo) {
430
+ const tabbarService = this.getTabbarService(holdInfo.side);
431
+ tabbarService.registerContainer(containerId, { views: holdInfo.views, options: holdInfo.options });
432
+ this.tabbarUpdateSet.delete(containerId);
433
+ this.holdTabbarComponent.delete(containerId);
434
+ }
435
+ }
436
+
437
+ replaceViewComponent(view: View, props?: any) {
438
+ const containerId = this.viewToContainerMap.get(view.id);
439
+ if (!containerId) {
440
+ this.debug.warn(
441
+ `The container corresponding to \`${view.id}\` was not found, please check the incoming parameters!`,
442
+ );
443
+ return;
444
+ }
445
+ const contributedView = this.customViews.get(view.id);
446
+ if (contributedView) {
447
+ view = Object.assign(contributedView, view);
448
+ }
449
+
450
+ this.collectViewComponent(view, containerId!, props, {
451
+ isReplace: true,
452
+ });
453
+ }
454
+
455
+ disposeViewComponent(viewId: string) {
456
+ const toDispose = this.disposableMap.get(viewId);
457
+
458
+ if (toDispose) {
459
+ toDispose.dispose();
460
+ }
461
+
462
+ const containerId = this.viewToContainerMap.get(viewId);
463
+ if (!containerId) {
464
+ this.debug.warn(
465
+ `The container corresponding to \`${viewId}\` was not found, please check the incoming parameters!`,
466
+ );
467
+ return;
468
+ }
469
+
470
+ const accordionService: AccordionService = this.getAccordionService(containerId);
471
+
472
+ accordionService.disposeView(viewId);
473
+ }
474
+
475
+ revealView(viewId: string) {
476
+ const containerId = this.viewToContainerMap.get(viewId);
477
+ if (!containerId) {
478
+ this.debug.warn(
479
+ `The container corresponding to \`${viewId}\` was not found, please check the incoming parameters!`,
480
+ );
481
+ return;
482
+ }
483
+ const accordionService: AccordionService = this.getAccordionService(containerId);
484
+ accordionService.revealView(viewId);
485
+ }
486
+
487
+ disposeContainer(containerId: string) {
488
+ let location: string | undefined;
489
+ for (const service of this.tabbarServices.values()) {
490
+ if (service.getContainer(containerId)) {
491
+ location = service.location;
492
+ break;
493
+ }
494
+ }
495
+ if (location) {
496
+ const tabbarService = this.getTabbarService(location);
497
+ tabbarService.disposeContainer(containerId);
498
+ } else {
499
+ this.debug.warn(`The Tabbar to the \`${containerId}\` was not found.`);
500
+ }
501
+ }
502
+
503
+ // TODO 这样很耦合,不能做到tab renderer自由拆分
504
+ expandBottom(expand: boolean): void {
505
+ const tabbarService = this.getTabbarService(SlotLocation.bottom);
506
+ tabbarService.doExpand(expand);
507
+ this.contextKeyService.createKey('bottomFullExpanded', tabbarService.isExpanded);
508
+ }
509
+
510
+ get bottomExpanded(): boolean {
511
+ const tabbarService = this.getTabbarService(SlotLocation.bottom);
512
+ this.contextKeyService.createKey('bottomFullExpanded', tabbarService.isExpanded);
513
+ return tabbarService.isExpanded;
514
+ }
515
+ }