@opensumi/ide-main-layout 3.9.1-next-1748523870.0 → 3.9.1-next-1748593694.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 (71) hide show
  1. package/README.md +30 -31
  2. package/lib/browser/accordion/accordion.service.d.ts +9 -1
  3. package/lib/browser/accordion/accordion.service.d.ts.map +1 -1
  4. package/lib/browser/accordion/accordion.service.js +120 -33
  5. package/lib/browser/accordion/accordion.service.js.map +1 -1
  6. package/lib/browser/accordion/accordion.view.d.ts.map +1 -1
  7. package/lib/browser/accordion/accordion.view.js +4 -1
  8. package/lib/browser/accordion/accordion.view.js.map +1 -1
  9. package/lib/browser/accordion/section.view.d.ts.map +1 -1
  10. package/lib/browser/accordion/section.view.js +1 -1
  11. package/lib/browser/accordion/section.view.js.map +1 -1
  12. package/lib/browser/accordion/styles.module.less +61 -0
  13. package/lib/browser/command.d.ts +29 -0
  14. package/lib/browser/command.d.ts.map +1 -0
  15. package/lib/browser/command.js +84 -0
  16. package/lib/browser/command.js.map +1 -0
  17. package/lib/browser/default-config.d.ts.map +1 -1
  18. package/lib/browser/default-config.js +7 -6
  19. package/lib/browser/default-config.js.map +1 -1
  20. package/lib/browser/drop-area/drop-area.d.ts +3 -2
  21. package/lib/browser/drop-area/drop-area.d.ts.map +1 -1
  22. package/lib/browser/drop-area/drop-area.js +8 -5
  23. package/lib/browser/drop-area/drop-area.js.map +1 -1
  24. package/lib/browser/index.d.ts.map +1 -1
  25. package/lib/browser/index.js.map +1 -1
  26. package/lib/browser/layout.service.d.ts +4 -0
  27. package/lib/browser/layout.service.d.ts.map +1 -1
  28. package/lib/browser/layout.service.js +140 -79
  29. package/lib/browser/layout.service.js.map +1 -1
  30. package/lib/browser/main-layout.contribution.d.ts +2 -18
  31. package/lib/browser/main-layout.contribution.d.ts.map +1 -1
  32. package/lib/browser/main-layout.contribution.js +75 -131
  33. package/lib/browser/main-layout.contribution.js.map +1 -1
  34. package/lib/browser/tabbar/TABBAR_CONFIG_USAGE.md +141 -0
  35. package/lib/browser/tabbar/bar.view.d.ts.map +1 -1
  36. package/lib/browser/tabbar/bar.view.js +5 -10
  37. package/lib/browser/tabbar/bar.view.js.map +1 -1
  38. package/lib/browser/tabbar/panel.view.js +2 -2
  39. package/lib/browser/tabbar/panel.view.js.map +1 -1
  40. package/lib/browser/tabbar/renderer.view.js +4 -4
  41. package/lib/browser/tabbar/renderer.view.js.map +1 -1
  42. package/lib/browser/tabbar/tabbar-behavior-handler.d.ts +71 -0
  43. package/lib/browser/tabbar/tabbar-behavior-handler.d.ts.map +1 -0
  44. package/lib/browser/tabbar/tabbar-behavior-handler.js +210 -0
  45. package/lib/browser/tabbar/tabbar-behavior-handler.js.map +1 -0
  46. package/lib/browser/tabbar/tabbar.service.d.ts +8 -19
  47. package/lib/browser/tabbar/tabbar.service.d.ts.map +1 -1
  48. package/lib/browser/tabbar/tabbar.service.js +39 -148
  49. package/lib/browser/tabbar/tabbar.service.js.map +1 -1
  50. package/lib/common/main-layout.definition.d.ts +4 -3
  51. package/lib/common/main-layout.definition.d.ts.map +1 -1
  52. package/lib/common/main-layout.definition.js +5 -4
  53. package/lib/common/main-layout.definition.js.map +1 -1
  54. package/package.json +8 -8
  55. package/src/browser/accordion/accordion.service.ts +152 -35
  56. package/src/browser/accordion/accordion.view.tsx +4 -2
  57. package/src/browser/accordion/section.view.tsx +5 -1
  58. package/src/browser/accordion/styles.module.less +61 -0
  59. package/src/browser/command.ts +99 -0
  60. package/src/browser/default-config.ts +8 -7
  61. package/src/browser/drop-area/drop-area.tsx +6 -3
  62. package/src/browser/index.ts +1 -0
  63. package/src/browser/layout.service.ts +156 -67
  64. package/src/browser/main-layout.contribution.ts +93 -138
  65. package/src/browser/tabbar/TABBAR_CONFIG_USAGE.md +141 -0
  66. package/src/browser/tabbar/bar.view.tsx +9 -15
  67. package/src/browser/tabbar/panel.view.tsx +2 -2
  68. package/src/browser/tabbar/renderer.view.tsx +4 -4
  69. package/src/browser/tabbar/tabbar-behavior-handler.ts +260 -0
  70. package/src/browser/tabbar/tabbar.service.ts +54 -163
  71. package/src/common/main-layout.definition.ts +6 -4
@@ -18,7 +18,7 @@ import {
18
18
  WithEventBus,
19
19
  slotRendererRegistry,
20
20
  } from '@opensumi/ide-core-browser';
21
- import { fixLayout } from '@opensumi/ide-core-browser/lib/components';
21
+ import { Layout, fixLayout } from '@opensumi/ide-core-browser/lib/components';
22
22
  import { LAYOUT_STATE, LayoutState } from '@opensumi/ide-core-browser/lib/layout/layout-state';
23
23
  import { ComponentRegistryInfo } from '@opensumi/ide-core-browser/lib/layout/layout.interface';
24
24
  import {
@@ -29,11 +29,13 @@ import {
29
29
  MenuId,
30
30
  } from '@opensumi/ide-core-browser/lib/menu/next';
31
31
  import { Deferred, getDebugLogger, isUndefined } from '@opensumi/ide-core-common';
32
+ import { transaction } from '@opensumi/ide-monaco/lib/common/observable';
32
33
  import { ThemeChangedEvent } from '@opensumi/ide-theme';
33
34
 
34
35
  import {
35
- DROP_BOTTOM_CONTAINER,
36
- DROP_RIGHT_CONTAINER,
36
+ DROP_EXTEND_VIEW_CONTAINER,
37
+ DROP_PANEL_CONTAINER,
38
+ DROP_VIEW_CONTAINER,
37
39
  IMainLayoutService,
38
40
  MainLayoutContribution,
39
41
  SUPPORT_ACCORDION_LOCATION,
@@ -45,19 +47,21 @@ import { TabbarService } from './tabbar/tabbar.service';
45
47
  import { TabBarHandler } from './tabbar-handler';
46
48
 
47
49
  const defaultLayoutState = {
48
- [SlotLocation.left]: {
50
+ [SlotLocation.view]: {
49
51
  currentId: undefined,
50
52
  size: undefined,
51
53
  },
52
- [SlotLocation.right]: {
54
+ [SlotLocation.extendView]: {
53
55
  // 依照下面的恢复逻辑,这里设置为 `''` 时,就不会恢复右侧的 TabBar 的状态(即选中相应的 viewContainer)
54
56
  currentId: '',
55
57
  size: undefined,
56
58
  },
57
- [SlotLocation.bottom]: {
59
+ [SlotLocation.panel]: {
58
60
  currentId: undefined,
59
61
  size: undefined,
60
62
  },
63
+ // 存储 Container 的移动信息:containerId -> location
64
+ containerLocations: {},
61
65
  };
62
66
 
63
67
  @Injectable()
@@ -111,6 +115,9 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
111
115
  };
112
116
  } = {};
113
117
 
118
+ // 记录正在恢复状态的 location,防止恢复过程中存储中间状态
119
+ private isRestoring = new Set<string>();
120
+
114
121
  private customViews = new Map<string, View>();
115
122
 
116
123
  private debug = getDebugLogger();
@@ -150,10 +157,16 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
150
157
  }
151
158
 
152
159
  storeState(service: TabbarService, currentId?: string) {
160
+ // 如果正在恢复中,跳过存储,避免存储中间状态
161
+ if (this.isRestoring.has(service.location)) {
162
+ return;
163
+ }
164
+
153
165
  this.state[service.location] = {
154
166
  currentId,
155
167
  size: service.prevSize,
156
168
  };
169
+
157
170
  this.layoutState.setState(LAYOUT_STATE.MAIN, this.state);
158
171
  }
159
172
 
@@ -174,63 +187,89 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
174
187
 
175
188
  restoreTabbarService = async (service: TabbarService) => {
176
189
  this.state = fixLayout(this.layoutState.getState(LAYOUT_STATE.MAIN, defaultLayoutState));
177
-
178
190
  const { currentId, size } = this.state[service.location] || {};
179
191
  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;
192
+
193
+ // 根据存储的容器位置信息恢复容器位置(drag & drop 移动过的)
194
+ if (this.state.containerLocations) {
195
+ for (const [containerId, targetLocation] of Object.entries(this.state.containerLocations)) {
196
+ if (targetLocation === service.location && !this.isDropContainer(containerId)) {
197
+ // 检查容器是否在其他 tabbar 中,如果是则移动到当前 tabbar
198
+ const expectTabbar = this.findTabbarServiceByContainerId(containerId);
199
+ if (expectTabbar && expectTabbar.location !== service.location) {
200
+ this.moveContainerTo(containerId, service.location);
195
201
  } else {
196
- this.logger.warn(`[defaultPanels] No \`${restorePanel}\` view found!`);
202
+ // 清理无用的状态
203
+ delete this.state.containerLocations[containerId];
204
+ this.layoutState.setState(LAYOUT_STATE.MAIN, this.state);
197
205
  }
198
206
  }
199
- } else {
200
- defaultContainer = '';
201
207
  }
202
208
  }
203
- /**
204
- * ContainerId 存在三种值类型,对应的处理模式如下:
205
- * 1. undefined: 采用首个注册的容器作为当前 containerId
206
- * 2. string: 非 drop container 直接使用该 containerId 作为当前 containerId
207
- * 3. '': 直接清空当前 containerId,不展开相应的 viewContainer
208
- */
209
+
210
+ const defaultContainer = this.getDefaultContainer(service);
211
+ this.restoreContainerId(service, currentId, defaultContainer);
212
+ };
213
+
214
+ private getDefaultContainer(service: TabbarService): string | undefined {
215
+ const defaultPanels = this.appConfig.defaultPanels;
216
+ const defaultPanel = defaultPanels && defaultPanels[service.location];
217
+
218
+ // 如果配置了默认面板,则使用配置的面板
219
+ if (defaultPanels && defaultPanel !== undefined) {
220
+ if (!defaultPanel) {
221
+ return '';
222
+ }
223
+ if (service.containersMap.has(defaultPanel)) {
224
+ return defaultPanel;
225
+ }
226
+
227
+ const componentInfo = this.componentRegistry.getComponentRegistryInfo(defaultPanel);
228
+ const isValidModule =
229
+ componentInfo && this.appConfig.layoutConfig[service.location]?.modules?.includes(defaultPanel);
230
+
231
+ if (isValidModule) {
232
+ return componentInfo.options!.containerId;
233
+ }
234
+
235
+ this.logger.warn(`[defaultPanels] No \`${defaultPanel}\` view found!`);
236
+ }
237
+
238
+ // 获取第一个可见容器作为默认值
239
+ return service.visibleContainers[0]?.options?.containerId;
240
+ }
241
+
242
+ private restoreContainerId(
243
+ service: TabbarService,
244
+ currentId: string | undefined,
245
+ defaultContainer: string | undefined,
246
+ ) {
209
247
  if (isUndefined(currentId)) {
248
+ // 未指定 currentId 时,使用默认容器或第一个注册的容器
210
249
  if (isUndefined(defaultContainer)) {
211
- // 默认采用首个注册的容器作为当前 containerId
212
250
  service.updateNextContainerId();
213
251
  } else {
214
252
  service.updateCurrentContainerId(defaultContainer);
215
253
  }
216
- } else if (currentId && !this.isDropContainer(currentId)) {
254
+ return;
255
+ }
256
+
257
+ if (currentId && !this.isDropContainer(currentId)) {
217
258
  if (service.containersMap.has(currentId)) {
218
259
  service.updateCurrentContainerId(currentId);
219
- } else {
220
- // 如果在别的 tabbar 中存在该 containerId,则将其移动到当前 tabbar
221
- if (this.findTabbarServiceByContainerId(currentId)) {
222
- this.moveContainerTo(currentId, service.location);
223
- service.updateCurrentContainerId(currentId);
224
- } else {
225
- service.updateCurrentContainerId(defaultContainer);
226
- // 等待后续新容器注册时,更新当前的 containerId
227
- service.updateNextContainerId(currentId);
228
- }
260
+ return;
229
261
  }
230
- } else if (currentId === '' || this.isDropContainer(currentId)) {
262
+
263
+ // 如果当前 containerId 不存在,则使用默认容器
264
+ service.updateCurrentContainerId(defaultContainer || '');
265
+ service.updateNextContainerId(currentId);
266
+ return;
267
+ }
268
+
269
+ if (currentId === '' || this.isDropContainer(currentId)) {
231
270
  service.updateCurrentContainerId('');
232
271
  }
233
- };
272
+ }
234
273
 
235
274
  findTabbarServiceByContainerId(containerId: string): TabbarService | undefined {
236
275
  let tabbarService: undefined | TabbarService;
@@ -256,7 +295,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
256
295
  this.logger.error(`cannot find container: ${containerId}`);
257
296
  return;
258
297
  }
259
- if (!container.options?.draggable) {
298
+ if (container.options?.draggable === false) {
260
299
  this.logger.warn(`container: ${containerId} is not draggable`);
261
300
  return;
262
301
  }
@@ -271,33 +310,59 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
271
310
  toTabbar.dynamicAddContainer(containerId, container);
272
311
  const newHandler = this.injector.get(TabBarHandler, [containerId, this.getTabbarService(toTabbar.location)]);
273
312
  this.handleMap.set(containerId, newHandler!);
313
+
314
+ // 更新容器位置信息
315
+ if (!this.state.containerLocations) {
316
+ this.state.containerLocations = {};
317
+ }
318
+ this.state.containerLocations[containerId] = to;
319
+ // this.layoutState.setState(LAYOUT_STATE.MAIN, this.state);
274
320
  }
275
321
 
276
322
  showDropAreaForContainer(containerId: string): void {
277
323
  const tabbarService = this.findTabbarServiceByContainerId(containerId);
278
- const bottomService = this.tabbarServices.get('bottom');
279
- const rightService = this.tabbarServices.get('right');
324
+ const panelService = this.tabbarServices.get(SlotLocation.panel);
325
+ const extendViewService = this.tabbarServices.get(SlotLocation.extendView);
326
+ const viewService = this.tabbarServices.get(SlotLocation.view);
280
327
  if (!tabbarService) {
281
328
  this.logger.error(`cannot find container: ${containerId}`);
282
329
  return;
283
330
  }
284
- if (tabbarService?.location === 'right') {
285
- bottomService?.updateCurrentContainerId(DROP_BOTTOM_CONTAINER);
286
- }
287
- if (tabbarService?.location === 'bottom') {
288
- rightService?.updateCurrentContainerId(DROP_RIGHT_CONTAINER);
289
- }
331
+ const otherServices = {
332
+ [SlotLocation.extendView]: [panelService, viewService],
333
+ [SlotLocation.panel]: [extendViewService, viewService],
334
+ [SlotLocation.view]: [panelService, extendViewService],
335
+ };
336
+
337
+ const dropContainers = {
338
+ [SlotLocation.panel]: DROP_PANEL_CONTAINER,
339
+ [SlotLocation.extendView]: DROP_EXTEND_VIEW_CONTAINER,
340
+ [SlotLocation.view]: DROP_VIEW_CONTAINER,
341
+ };
342
+
343
+ transaction((tx) => {
344
+ otherServices[tabbarService?.location]?.forEach((service) => {
345
+ service?.updateCurrentContainerId(dropContainers[service?.location], tx);
346
+ });
347
+ });
290
348
  }
291
349
 
292
350
  hideDropArea(): void {
293
- const bottomService = this.tabbarServices.get('bottom');
294
- const rightService = this.tabbarServices.get('right');
295
- if (bottomService?.currentContainerId.get() === DROP_BOTTOM_CONTAINER) {
296
- bottomService.updateCurrentContainerId(bottomService.previousContainerId || '');
297
- }
298
- if (rightService?.currentContainerId.get() === DROP_RIGHT_CONTAINER) {
299
- rightService.updateCurrentContainerId(rightService.previousContainerId || '');
300
- }
351
+ const panelService = this.tabbarServices.get(SlotLocation.panel);
352
+ const extendViewService = this.tabbarServices.get(SlotLocation.extendView);
353
+ const viewService = this.tabbarServices.get(SlotLocation.view);
354
+
355
+ transaction((tx) => {
356
+ if (panelService?.currentContainerId.get() === DROP_PANEL_CONTAINER) {
357
+ panelService.updateCurrentContainerId(panelService.previousContainerId || '', tx);
358
+ }
359
+ if (extendViewService?.currentContainerId.get() === DROP_EXTEND_VIEW_CONTAINER) {
360
+ extendViewService.updateCurrentContainerId(extendViewService.previousContainerId || '', tx);
361
+ }
362
+ if (viewService?.currentContainerId.get() === DROP_VIEW_CONTAINER) {
363
+ viewService.updateCurrentContainerId(viewService.previousContainerId || '', tx);
364
+ }
365
+ });
301
366
  }
302
367
 
303
368
  isVisible(location: string) {
@@ -336,7 +401,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
336
401
  }
337
402
 
338
403
  private isDropContainer(containerId: string): boolean {
339
- return [DROP_BOTTOM_CONTAINER, DROP_RIGHT_CONTAINER].includes(containerId);
404
+ return [DROP_PANEL_CONTAINER, DROP_EXTEND_VIEW_CONTAINER, DROP_VIEW_CONTAINER].includes(containerId);
340
405
  }
341
406
 
342
407
  private findNonDropContainerId(tabbarService: TabbarService): string {
@@ -375,14 +440,24 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
375
440
  }),
376
441
  );
377
442
  service.viewReady.promise
378
- .then(() => service.restoreState())
443
+ .then(() => {
444
+ // 标记开始恢复,防止恢复过程中存储中间状态
445
+ this.isRestoring.add(service.location);
446
+ return service.restoreState();
447
+ })
379
448
  .then(() => this.restoreTabbarService(service))
449
+ .then(() => {
450
+ // 恢复完成,清除标记
451
+ this.isRestoring.delete(service.location);
452
+ })
380
453
  .catch((err) => {
454
+ // 出错时也要清除标记
455
+ this.isRestoring.delete(service.location);
381
456
  this.logger.error(`[TabbarService:${location}] restore state error`, err);
382
457
  });
383
458
  const debouncedStoreState = debounce(() => this.storeState(service, service.currentContainerId.get()), 100);
384
459
  service.addDispose(service.onSizeChange(debouncedStoreState));
385
- if (location === SlotLocation.bottom) {
460
+ if (location === SlotLocation.panel) {
386
461
  // use this getter's side effect to set bottomExpanded contextKey
387
462
  const debouncedUpdate = debounce(() => void this.bottomExpanded, 100);
388
463
  service.addDispose(service.onSizeChange(() => debouncedUpdate));
@@ -463,6 +538,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
463
538
  if (Fc) {
464
539
  this.debug.warn('collectTabbarComponent api warning: Please move react component into options.component!');
465
540
  }
541
+ side = this.mapSideToLocation(side);
466
542
  if (options.hideIfEmpty && !views.length && !options.component) {
467
543
  this.holdTabbarComponent.set(options.containerId, { views, options, side });
468
544
  if (this.tabbarUpdateSet.has(options.containerId)) {
@@ -495,6 +571,19 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
495
571
  return options.containerId;
496
572
  }
497
573
 
574
+ private mapSideToLocation(side: string): SlotLocation {
575
+ switch (side) {
576
+ case 'left':
577
+ return SlotLocation.view;
578
+ case 'right':
579
+ return SlotLocation.extendView;
580
+ case 'bottom':
581
+ return SlotLocation.panel;
582
+ default:
583
+ return side;
584
+ }
585
+ }
586
+
498
587
  getViewAccordionService(viewId: string) {
499
588
  const containerId = this.viewToContainerMap.get(viewId);
500
589
  if (!containerId) {
@@ -640,7 +729,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
640
729
 
641
730
  // TODO 这样很耦合,不能做到tab renderer自由拆分
642
731
  expandBottom(expand: boolean): void {
643
- const tabbarService = this.getTabbarService(SlotLocation.bottom);
732
+ const tabbarService = this.getTabbarService(SlotLocation.panel);
644
733
  if (!tabbarService.currentContainerId.get()) {
645
734
  tabbarService.updateCurrentContainerId(
646
735
  tabbarService.currentContainerId.get() ||
@@ -653,7 +742,7 @@ export class LayoutService extends WithEventBus implements IMainLayoutService {
653
742
  }
654
743
 
655
744
  get bottomExpanded(): boolean {
656
- const tabbarService = this.getTabbarService(SlotLocation.bottom);
745
+ const tabbarService = this.getTabbarService(SlotLocation.panel);
657
746
  this.contextKeyService.createKey('bottomFullExpanded', tabbarService.isExpanded);
658
747
  return tabbarService.isExpanded;
659
748
  }