@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
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@opensumi/ide-main-layout",
3
- "version": "2.21.13",
3
+ "version": "2.22.0",
4
4
  "description": "@opensumi/ide-main-layout",
5
5
  "files": [
6
- "lib"
6
+ "lib",
7
+ "src"
7
8
  ],
8
9
  "license": "MIT",
9
10
  "main": "lib/index.js",
10
11
  "typings": "lib/index.d.ts",
11
12
  "scripts": {
12
- "prepublishOnly": "npm run build",
13
+ "prepublishOnly": "yarn run build",
13
14
  "build": "tsc --build ../../configs/ts/references/tsconfig.main-layout.json"
14
15
  },
15
16
  "repository": {
@@ -17,13 +18,13 @@
17
18
  "url": "git@github.com:opensumi/core.git"
18
19
  },
19
20
  "dependencies": {
20
- "@opensumi/ide-components": "2.21.13",
21
- "@opensumi/ide-core-common": "2.21.13"
21
+ "@opensumi/ide-components": "2.22.0",
22
+ "@opensumi/ide-core-common": "2.22.0"
22
23
  },
23
24
  "devDependencies": {
24
- "@opensumi/ide-core-browser": "2.21.13",
25
- "@opensumi/ide-dev-tool": "^1.3.1",
26
- "@opensumi/ide-theme": "2.21.13"
25
+ "@opensumi/ide-core-browser": "2.22.0",
26
+ "@opensumi/ide-dev-tool": "1.3.1",
27
+ "@opensumi/ide-theme": "2.22.0"
27
28
  },
28
- "gitHead": "1480afacfef7ef11eae65c5cbbddce416f227fe8"
29
+ "gitHead": "927717e1c60f9cf5f01622fd51085b318a175eeb"
29
30
  }
@@ -0,0 +1,635 @@
1
+ import debounce from 'lodash/debounce';
2
+ import { action, observable } from 'mobx';
3
+
4
+ import { Injectable, Autowired } from '@opensumi/di';
5
+ import {
6
+ View,
7
+ CommandRegistry,
8
+ ViewContextKeyRegistry,
9
+ IContextKeyService,
10
+ localize,
11
+ IContextKey,
12
+ OnEvent,
13
+ WithEventBus,
14
+ ResizeEvent,
15
+ DisposableCollection,
16
+ ContextKeyChangeEvent,
17
+ Event,
18
+ Emitter,
19
+ IScopedContextKeyService,
20
+ } from '@opensumi/ide-core-browser';
21
+ import { RESIZE_LOCK } from '@opensumi/ide-core-browser/lib/components';
22
+ import {
23
+ SplitPanelManager,
24
+ SplitPanelService,
25
+ } from '@opensumi/ide-core-browser/lib/components/layout/split-panel.service';
26
+ import { LayoutState, LAYOUT_STATE } from '@opensumi/ide-core-browser/lib/layout/layout-state';
27
+ import {
28
+ AbstractContextMenuService,
29
+ AbstractMenuService,
30
+ IMenu,
31
+ IMenuRegistry,
32
+ ICtxMenuRenderer,
33
+ MenuId,
34
+ } from '@opensumi/ide-core-browser/lib/menu/next';
35
+ import { IProgressService } from '@opensumi/ide-core-browser/lib/progress';
36
+
37
+ import { ViewCollapseChangedEvent } from '../../common';
38
+
39
+ export interface SectionState {
40
+ collapsed: boolean;
41
+ hidden: boolean;
42
+ size?: number;
43
+ nextSize?: number;
44
+ }
45
+
46
+ interface AccordionViewChangeEvent {
47
+ id: string;
48
+ title?: string;
49
+ description?: string;
50
+ message?: string;
51
+ }
52
+
53
+ @Injectable({ multiple: true })
54
+ export class AccordionService extends WithEventBus {
55
+ @Autowired()
56
+ protected splitPanelManager: SplitPanelManager;
57
+
58
+ @Autowired(AbstractMenuService)
59
+ protected menuService: AbstractMenuService;
60
+
61
+ @Autowired(AbstractContextMenuService)
62
+ protected ctxMenuService: AbstractContextMenuService;
63
+
64
+ @Autowired(IMenuRegistry)
65
+ protected menuRegistry: IMenuRegistry;
66
+
67
+ @Autowired(CommandRegistry)
68
+ private commandRegistry: CommandRegistry;
69
+
70
+ @Autowired(ICtxMenuRenderer)
71
+ private readonly contextMenuRenderer: ICtxMenuRenderer;
72
+
73
+ @Autowired()
74
+ private viewContextKeyRegistry: ViewContextKeyRegistry;
75
+
76
+ @Autowired(IContextKeyService)
77
+ private contextKeyService: IContextKeyService;
78
+
79
+ @Autowired()
80
+ private layoutState: LayoutState;
81
+
82
+ @Autowired(IProgressService)
83
+ private progressService: IProgressService;
84
+
85
+ protected splitPanelService: SplitPanelService;
86
+
87
+ // 用于强制显示功能的contextKey
88
+ private forceRevealContextKeys = new Map<string, { when: string; key: IContextKey<boolean> }>();
89
+ // 所有View的when条件集合
90
+ private viewWhenContextkeys = new Set<string>();
91
+ // 带contextKey且已渲染的视图
92
+ private appendedViewSet = new Set<string>();
93
+ // 所有带contextKey视图
94
+ private viewsWithContextKey = new Set<View>();
95
+
96
+ @observable.shallow views: View[] = [];
97
+
98
+ @observable state: { [viewId: string]: SectionState } = {};
99
+
100
+ rendered = false;
101
+
102
+ private headerSize: number;
103
+ private minSize: number;
104
+ private menuId = `${MenuId.AccordionContext}/${this.containerId}`;
105
+ private toDispose: Map<string, DisposableCollection> = new Map();
106
+
107
+ private topViewKey: IContextKey<string>;
108
+ private scopedCtxKeyService: IScopedContextKeyService;
109
+
110
+ private didChangeViewTitleEmitter: Emitter<AccordionViewChangeEvent> = new Emitter<AccordionViewChangeEvent>();
111
+ public onDidChangeViewTiele: Event<AccordionViewChangeEvent> = this.didChangeViewTitleEmitter.event;
112
+
113
+ private beforeAppendViewEmitter = new Emitter<string>();
114
+ public onBeforeAppendViewEvent = this.beforeAppendViewEmitter.event;
115
+
116
+ private afterAppendViewEmitter = new Emitter<string>();
117
+ public onAfterAppendViewEvent = this.afterAppendViewEmitter.event;
118
+
119
+ private afterDisposeViewEmitter = new Emitter<string>();
120
+ public onAfterDisposeViewEvent = this.afterDisposeViewEmitter.event;
121
+
122
+ constructor(public containerId: string, private noRestore?: boolean) {
123
+ super();
124
+ this.splitPanelService = this.splitPanelManager.getService(containerId);
125
+ this.scopedCtxKeyService = this.contextKeyService.createScoped();
126
+ this.scopedCtxKeyService.createKey('triggerWithSection', true);
127
+ this.menuRegistry.registerMenuItem(this.menuId, {
128
+ command: {
129
+ id: this.registerGlobalToggleCommand(),
130
+ label: localize('layout.view.hide', 'Hide'),
131
+ },
132
+ group: '0_global',
133
+ when: 'triggerWithSection == true',
134
+ });
135
+ this.viewContextKeyRegistry.afterContextKeyServiceRegistered(this.containerId, (contextKeyService) => {
136
+ this.topViewKey = contextKeyService!.createKey('view', containerId);
137
+ setTimeout(() => {
138
+ // 由于tabbar.service会立刻设置view,这边要等下一个event loop
139
+ this.popViewKeyIfOnlyOneViewVisible();
140
+ });
141
+ });
142
+ this.addDispose(
143
+ Event.debounce<ContextKeyChangeEvent, boolean>(
144
+ this.contextKeyService.onDidChangeContext,
145
+ (last, event) => last || event.payload.affectsSome(this.viewWhenContextkeys),
146
+ 50,
147
+ )((e) => e && this.handleContextKeyChange(), this),
148
+ );
149
+ this.listenWindowResize();
150
+ }
151
+
152
+ updateViewTitle(viewId: string, title: string) {
153
+ this.didChangeViewTitleEmitter.fire({ id: viewId, title });
154
+ }
155
+
156
+ updateViewDesciption(viewId: string, desc: string) {
157
+ this.didChangeViewTitleEmitter.fire({ id: viewId, description: desc });
158
+ }
159
+
160
+ updateViewMessage(viewId: string, msg: string) {
161
+ this.didChangeViewTitleEmitter.fire({ id: viewId, message: msg });
162
+ }
163
+
164
+ tryUpdateResize() {
165
+ this.doUpdateResize();
166
+ }
167
+
168
+ restoreState() {
169
+ if (this.noRestore) {
170
+ return;
171
+ }
172
+ const defaultState: { [containerId: string]: SectionState } = {};
173
+ this.visibleViews.forEach((view) => (defaultState[view.id] = { collapsed: false, hidden: false }));
174
+ const restoredState = this.layoutState.getState(LAYOUT_STATE.getContainerSpace(this.containerId), defaultState);
175
+ if (restoredState !== defaultState) {
176
+ this.state = restoredState;
177
+ }
178
+ this.popViewKeyIfOnlyOneViewVisible();
179
+ this.restoreSize();
180
+ this.rendered = true;
181
+ }
182
+
183
+ // 调用时需要保证dom可见
184
+ restoreSize() {
185
+ // 计算存储总高度与当前窗口总高度差,加到最后一个展开的面板
186
+ let availableSize = this.splitPanelService.rootNode?.clientHeight || 0;
187
+ let finalUncollapsedIndex: number | undefined;
188
+ this.visibleViews.forEach((view, index) => {
189
+ const savedState = this.state[view.id];
190
+ if (savedState.collapsed) {
191
+ this.setSize(index, 0, false, true);
192
+ availableSize -= this.headerSize;
193
+ } else if (!savedState.collapsed && savedState.size) {
194
+ this.setSize(index, savedState.size, false, true);
195
+ availableSize -= savedState.size;
196
+ finalUncollapsedIndex = index;
197
+ }
198
+ });
199
+ if (finalUncollapsedIndex) {
200
+ this.setSize(
201
+ finalUncollapsedIndex,
202
+ this.state[this.visibleViews[finalUncollapsedIndex].id].size! + availableSize,
203
+ );
204
+ }
205
+ }
206
+
207
+ initConfig(config: { headerSize: number; minSize: number }) {
208
+ const { headerSize, minSize } = config;
209
+ this.headerSize = headerSize;
210
+ this.minSize = minSize;
211
+ }
212
+
213
+ private registerContextService(viewId: string) {
214
+ let scopedCtxKey = this.viewContextKeyRegistry.getContextKeyService(viewId);
215
+ if (!scopedCtxKey) {
216
+ scopedCtxKey = this.contextKeyService.createScoped();
217
+ scopedCtxKey.createKey('view', viewId);
218
+ this.viewContextKeyRegistry.registerContextKeyService(viewId, scopedCtxKey);
219
+ }
220
+ return scopedCtxKey;
221
+ }
222
+
223
+ getSectionToolbarMenu(viewId: string): IMenu {
224
+ // 确保在较早获取菜单时已经注册了 ScopedContextKeyService
225
+ const scopedCtxKey = this.registerContextService(viewId);
226
+ const menu = this.menuService.createMenu(MenuId.ViewTitle, scopedCtxKey);
227
+ const existingView = this.views.find((view) => view.id === viewId);
228
+ if (existingView) {
229
+ existingView.titleMenu = menu;
230
+ }
231
+ return menu;
232
+ }
233
+
234
+ private updateView(view: View) {
235
+ if (view.priority) {
236
+ const index = this.views.findIndex((value) => (value.priority || 0) < (view.priority || 0));
237
+ this.views.splice(index === -1 ? this.views.length : index, 0, view);
238
+ } else {
239
+ this.views.push(view);
240
+ }
241
+ }
242
+
243
+ appendView(view: View, replace?: boolean) {
244
+ if (this.appendedViewSet.has(view.id) && !replace) {
245
+ return;
246
+ }
247
+ const disposables = new DisposableCollection();
248
+ disposables.push(this.progressService.registerProgressIndicator(view.id));
249
+ // 已存在的viewId直接替换
250
+ const existIndex = this.views.findIndex((item) => item.id === view.id);
251
+ if (existIndex !== -1) {
252
+ this.views[existIndex] = Object.assign({}, this.views[existIndex], view);
253
+ return;
254
+ }
255
+ // 带contextKey视图需要先判断下
256
+ if (view.when) {
257
+ this.viewsWithContextKey.add(view);
258
+ // 强制显示的contextKey
259
+ const forceRevealExpr = this.createRevealContextKey(view.id);
260
+ this.fillKeysInWhenExpr(this.viewWhenContextkeys, view.when);
261
+ // 如果不匹配则跳过
262
+ if (!this.contextKeyService.match(view.when) && !this.contextKeyService.match(forceRevealExpr)) {
263
+ return;
264
+ }
265
+ this.appendedViewSet.add(view.id);
266
+ }
267
+ this.beforeAppendViewEmitter.fire(view.id);
268
+ this.updateView(view);
269
+
270
+ // 创建 scopedContextKeyService
271
+ this.registerContextService(view.id);
272
+
273
+ disposables.push(
274
+ this.menuRegistry.registerMenuItem(this.menuId, {
275
+ command: {
276
+ id: this.registerVisibleToggleCommand(view.id, disposables),
277
+ label: (view.name || view.id).toUpperCase(),
278
+ },
279
+ group: '1_widgets',
280
+ // TODO order计算
281
+ }),
282
+ );
283
+ this.toDispose.set(view.id, disposables);
284
+ this.popViewKeyIfOnlyOneViewVisible();
285
+ this.afterAppendViewEmitter.fire(view.id);
286
+ }
287
+
288
+ disposeView(viewId: string) {
289
+ const existIndex = this.views.findIndex((item) => item.id === viewId);
290
+ if (existIndex > -1) {
291
+ this.views.splice(existIndex, 1);
292
+ }
293
+ const disposable = this.toDispose.get(viewId);
294
+ if (disposable) {
295
+ disposable.dispose();
296
+ }
297
+ this.appendedViewSet.delete(viewId);
298
+ this.popViewKeyIfOnlyOneViewVisible();
299
+ this.afterDisposeViewEmitter.fire(viewId);
300
+ }
301
+
302
+ revealView(viewId: string) {
303
+ const target = this.forceRevealContextKeys.get(viewId);
304
+ if (target) {
305
+ target.key.set(true);
306
+ }
307
+ }
308
+
309
+ disposeAll() {
310
+ this.views = [];
311
+ this.toDispose.forEach((disposable) => {
312
+ disposable.dispose();
313
+ });
314
+ }
315
+
316
+ @OnEvent(ResizeEvent)
317
+ protected onResize(e: ResizeEvent) {
318
+ // 监听来自resize组件的事件
319
+ if (e.payload.slotLocation) {
320
+ if (this.state[e.payload.slotLocation]) {
321
+ const id = e.payload.slotLocation;
322
+ // get dom of viewId
323
+ const sectionDom = document.getElementById(id);
324
+ if (sectionDom) {
325
+ this.state[id].size = sectionDom.clientHeight;
326
+ this.storeState();
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ private doUpdateResize = debounce(() => {
333
+ let largestViewId: string | undefined;
334
+ Object.keys(this.state).forEach((id) => {
335
+ if (!(this.state[id].hidden || this.state[id].collapsed)) {
336
+ if (!largestViewId) {
337
+ largestViewId = id;
338
+ } else {
339
+ if ((this.state[id].size || 0) > (this.state[largestViewId].size || 0)) {
340
+ largestViewId = id;
341
+ }
342
+ }
343
+ }
344
+ });
345
+ if (largestViewId && this.splitPanelService.isVisible && this.expandedViews.length > 1) {
346
+ // 需要过滤掉没有实际注册的视图
347
+ const diffSize = this.splitPanelService.rootNode!.clientHeight - this.getPanelFullHeight();
348
+ if (diffSize) {
349
+ this.state[largestViewId].size! += diffSize;
350
+ this.toggleOpen(largestViewId!, false);
351
+ }
352
+ }
353
+ }, 16);
354
+
355
+ private getPanelFullHeight(ignoreViewId?: string) {
356
+ return Object.keys(this.state)
357
+ .filter((viewId) => this.views.find((item) => item.id === viewId) && viewId !== ignoreViewId)
358
+ .reduce(
359
+ (acc, id) =>
360
+ acc + (this.state[id].collapsed ? this.headerSize : this.state[id].hidden ? 0 : this.state[id].size!),
361
+ 0,
362
+ );
363
+ }
364
+
365
+ protected listenWindowResize() {
366
+ window.addEventListener('resize', this.doUpdateResize);
367
+ this.addDispose({ dispose: () => window.removeEventListener('resize', this.doUpdateResize) });
368
+ }
369
+
370
+ private createRevealContextKey(viewId: string) {
371
+ const forceRevealKey = `forceShow.${viewId}`;
372
+ this.forceRevealContextKeys.set(viewId, {
373
+ when: `${forceRevealKey} == true`,
374
+ key: this.contextKeyService.createKey(forceRevealKey, false),
375
+ });
376
+ this.viewWhenContextkeys.add(forceRevealKey);
377
+ return `${forceRevealKey} == true`;
378
+ }
379
+
380
+ protected storeState = debounce(() => {
381
+ if (this.noRestore || !this.rendered) {
382
+ return;
383
+ }
384
+ this.layoutState.setState(LAYOUT_STATE.getContainerSpace(this.containerId), this.state);
385
+ }, 200);
386
+
387
+ private registerGlobalToggleCommand() {
388
+ const commandId = `view-container.hide.${this.containerId}`;
389
+ this.commandRegistry.registerCommand(
390
+ {
391
+ id: commandId,
392
+ },
393
+ {
394
+ execute: ({ viewId }: { viewId: string }) => {
395
+ this.doToggleView(viewId);
396
+ },
397
+ isEnabled: () => this.visibleViews.length > 1,
398
+ },
399
+ );
400
+ return commandId;
401
+ }
402
+
403
+ private registerVisibleToggleCommand(viewId: string, disposables: DisposableCollection): string {
404
+ const commandId = `view-container.hide.${viewId}`;
405
+
406
+ disposables.push(
407
+ this.commandRegistry.registerCommand(
408
+ {
409
+ id: commandId,
410
+ },
411
+ {
412
+ execute: ({ forceShow }: { forceShow?: boolean } = {}) => {
413
+ this.doToggleView(viewId, forceShow);
414
+ },
415
+ isToggled: () => {
416
+ const state = this.getViewState(viewId);
417
+ return !state.hidden;
418
+ },
419
+ isEnabled: () => {
420
+ const state = this.getViewState(viewId);
421
+ return state.hidden || this.visibleViews.length > 1;
422
+ },
423
+ },
424
+ ),
425
+ );
426
+
427
+ return commandId;
428
+ }
429
+
430
+ protected doToggleView(viewId: string, forceShow?: boolean) {
431
+ const state = this.getViewState(viewId);
432
+ let nextState: boolean;
433
+ if (forceShow === undefined) {
434
+ nextState = !state.hidden;
435
+ } else {
436
+ nextState = !forceShow;
437
+ }
438
+ state.hidden = nextState;
439
+ this.popViewKeyIfOnlyOneViewVisible();
440
+ this.storeState();
441
+ }
442
+
443
+ popViewKeyIfOnlyOneViewVisible() {
444
+ if (!this.topViewKey) {
445
+ // 可能还没初始化
446
+ return;
447
+ }
448
+ const visibleViews = this.visibleViews;
449
+ if (visibleViews.length === 1) {
450
+ this.topViewKey.set(visibleViews[0].id);
451
+ } else {
452
+ this.topViewKey.reset();
453
+ }
454
+ }
455
+
456
+ toggleViewVisibility(viewId: string, show?: boolean) {
457
+ this.doToggleView(viewId, show);
458
+ }
459
+
460
+ get visibleViews(): View[] {
461
+ return this.views.filter((view) => {
462
+ const viewState = this.getViewState(view.id);
463
+ return !viewState.hidden;
464
+ });
465
+ }
466
+
467
+ get expandedViews(): View[] {
468
+ return this.views.filter((view) => {
469
+ const viewState = this.state[view.id];
470
+ return !viewState || (viewState && !viewState.hidden && !viewState.collapsed);
471
+ });
472
+ }
473
+
474
+ @action toggleOpen(viewId: string, collapsed: boolean) {
475
+ const index = this.visibleViews.findIndex((view) => view.id === viewId);
476
+ if (index > -1) {
477
+ this.doToggleOpen(viewId, collapsed, index, true);
478
+ }
479
+ }
480
+
481
+ @action.bound handleSectionClick(viewId: string, collapsed: boolean, index: number) {
482
+ this.doToggleOpen(viewId, collapsed, index);
483
+ }
484
+
485
+ @action.bound handleContextMenu(event: React.MouseEvent, viewId?: string) {
486
+ event.preventDefault();
487
+ const menus = this.ctxMenuService.createMenu({
488
+ id: this.menuId,
489
+ config: { args: [{ viewId }] },
490
+ contextKeyService: viewId ? this.viewContextKeyRegistry.getContextKeyService(viewId) : undefined,
491
+ });
492
+ const menuNodes = menus.getGroupedMenuNodes();
493
+ menus.dispose();
494
+ this.contextMenuRenderer.show({
495
+ menuNodes: menuNodes[1],
496
+ anchor: {
497
+ x: event.clientX,
498
+ y: event.clientY,
499
+ },
500
+ });
501
+ }
502
+
503
+ public getViewState(viewId: string) {
504
+ let viewState = this.state[viewId];
505
+ const view = this.views.find((item) => item.id === viewId);
506
+ if (!viewState) {
507
+ this.state[viewId] = { collapsed: view?.collapsed || false, hidden: view?.hidden || false };
508
+ viewState = this.state[viewId]!;
509
+ }
510
+ return viewState;
511
+ }
512
+
513
+ protected doToggleOpen(viewId: string, collapsed: boolean, index: number, noAnimation?: boolean) {
514
+ const viewState = this.getViewState(viewId);
515
+ viewState.collapsed = collapsed;
516
+ let sizeIncrement: number;
517
+ if (collapsed) {
518
+ sizeIncrement = this.setSize(index, 0, false, noAnimation);
519
+ } else {
520
+ // 仅有一个视图展开时独占
521
+ sizeIncrement = this.setSize(
522
+ index,
523
+ this.expandedViews.length === 1 ? this.getAvailableSize() : viewState.size || this.minSize,
524
+ false,
525
+ noAnimation,
526
+ );
527
+ }
528
+ // 下方视图被影响的情况下,上方视图不会同时变化,该情况会在sizeIncrement=0上体现
529
+ // 从视图下方最后一个展开的视图起依次减去对应的高度
530
+ for (let i = this.visibleViews.length - 1; i > index; i--) {
531
+ if (this.getViewState(this.visibleViews[i].id).collapsed !== true) {
532
+ sizeIncrement = this.setSize(i, sizeIncrement, true, noAnimation);
533
+ } else {
534
+ this.setSize(i, 0, false, noAnimation);
535
+ }
536
+ }
537
+ // 找到视图上方首个展开的视图减去对应的高度
538
+ for (let i = index - 1; i >= 0; i--) {
539
+ if ((this.state[this.visibleViews[i].id] || {}).collapsed !== true) {
540
+ sizeIncrement = this.setSize(i, sizeIncrement, true, noAnimation);
541
+ } else {
542
+ this.setSize(i, 0, false, noAnimation);
543
+ }
544
+ }
545
+
546
+ this.eventBus.fire(
547
+ new ViewCollapseChangedEvent({
548
+ viewId,
549
+ collapsed: viewState.collapsed,
550
+ }),
551
+ );
552
+ }
553
+
554
+ protected setSize(index: number, targetSize: number, isIncrement?: boolean, noAnimation?: boolean): number {
555
+ const fullHeight = this.splitPanelService.rootNode!.clientHeight;
556
+ const panel = this.splitPanelService.panels[index];
557
+ if (!noAnimation) {
558
+ panel.classList.add('resize-ease');
559
+ }
560
+ if (!targetSize && !isIncrement) {
561
+ targetSize = this.headerSize;
562
+ panel.classList.add(RESIZE_LOCK);
563
+ } else {
564
+ panel.classList.remove(RESIZE_LOCK);
565
+ }
566
+ // clientHeight会被上次展开的元素挤掉
567
+ const prevSize = panel.clientHeight;
568
+ const viewState = this.getViewState(this.visibleViews[index].id);
569
+ let calcTargetSize: number = targetSize;
570
+ // 视图即将折叠时、受其他视图影响尺寸变化时、主动展开时、resize时均需要存储尺寸信息
571
+ if (isIncrement) {
572
+ calcTargetSize = Math.max(prevSize - targetSize, this.minSize);
573
+ }
574
+ if (index === this.expandedViews.length - 1) {
575
+ // 最后一个视图需要兼容最大高度超出总视图高度及最大高度不足总视图高度的情况
576
+ if (calcTargetSize + index * this.minSize > fullHeight) {
577
+ calcTargetSize -= calcTargetSize + index * this.minSize - fullHeight;
578
+ } else {
579
+ const restSize = this.getPanelFullHeight(this.visibleViews[index].id);
580
+ if (calcTargetSize + restSize < fullHeight) {
581
+ calcTargetSize += fullHeight - (calcTargetSize + restSize);
582
+ }
583
+ }
584
+ }
585
+ if (this.rendered) {
586
+ let toSaveSize: number;
587
+ if (targetSize === this.headerSize) {
588
+ // 当前视图即将折叠且不是唯一展开的视图时,存储当前高度
589
+ toSaveSize = prevSize;
590
+ } else {
591
+ toSaveSize = calcTargetSize;
592
+ }
593
+ if (toSaveSize !== this.headerSize) {
594
+ // 视图折叠高度不做存储
595
+ viewState.size = toSaveSize;
596
+ }
597
+ }
598
+ this.storeState();
599
+ viewState.nextSize = calcTargetSize;
600
+ if (!noAnimation) {
601
+ setTimeout(() => {
602
+ // 动画 0.1s,保证结束后移除
603
+ panel.classList.remove('resize-ease');
604
+ }, 200);
605
+ }
606
+ return isIncrement ? calcTargetSize - (prevSize - targetSize) : targetSize - prevSize;
607
+ }
608
+
609
+ protected getAvailableSize() {
610
+ const fullHeight = this.splitPanelService.rootNode!.clientHeight;
611
+ return fullHeight - (this.visibleViews.length - 1) * this.headerSize;
612
+ }
613
+
614
+ private handleContextKeyChange() {
615
+ Array.from(this.viewsWithContextKey.values()).forEach((view) => {
616
+ if (
617
+ this.contextKeyService.match(view.when) ||
618
+ this.contextKeyService.match(this.forceRevealContextKeys.get(view.id)!.when)
619
+ ) {
620
+ this.appendView(view);
621
+ } else {
622
+ this.disposeView(view.id);
623
+ }
624
+ });
625
+ }
626
+
627
+ private fillKeysInWhenExpr(set: Set<string>, when?: string) {
628
+ const keys = this.contextKeyService.getKeysInWhen(when);
629
+ keys.forEach((key) => {
630
+ set.add(key);
631
+ });
632
+ }
633
+ }
634
+
635
+ export const AccordionServiceFactory = Symbol('AccordionServiceFactory');