Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/compositeBarActions.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { localize } from '../../../nls.js';
7
import { Action, IAction, Separator } from '../../../base/common/actions.js';
8
import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from '../../../base/browser/dom.js';
9
import { ICommandService } from '../../../platform/commands/common/commands.js';
10
import { toDisposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
11
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
12
import { IThemeService, IColorTheme } from '../../../platform/theme/common/themeService.js';
13
import { NumberBadge, IBadge, IActivity, ProgressBadge, IconBadge } from '../../services/activity/common/activity.js';
14
import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';
15
import { DelayedDragHandler } from '../../../base/browser/dnd.js';
16
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
17
import { Emitter, Event } from '../../../base/common/event.js';
18
import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDropEffect } from '../dnd.js';
19
import { Color } from '../../../base/common/color.js';
20
import { BaseActionViewItem, IActionViewItemOptions } from '../../../base/browser/ui/actionbar/actionViewItems.js';
21
import { Codicon } from '../../../base/common/codicons.js';
22
import { ThemeIcon } from '../../../base/common/themables.js';
23
import { IHoverService } from '../../../platform/hover/browser/hover.js';
24
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
25
import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js';
26
import { URI } from '../../../base/common/uri.js';
27
import { badgeBackground, badgeForeground, contrastBorder } from '../../../platform/theme/common/colorRegistry.js';
28
import { Action2, IAction2Options } from '../../../platform/actions/common/actions.js';
29
import { ViewContainerLocation } from '../../common/views.js';
30
import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js';
31
import { createConfigureKeybindingAction } from '../../../platform/actions/common/menuService.js';
32
33
export interface ICompositeBar {
34
35
/**
36
* Unpins a composite from the composite bar.
37
*/
38
unpin(compositeId: string): void;
39
40
/**
41
* Pin a composite inside the composite bar.
42
*/
43
pin(compositeId: string): void;
44
45
/**
46
* Find out if a composite is pinned in the composite bar.
47
*/
48
isPinned(compositeId: string): boolean;
49
50
/**
51
* Get pinned composite ids in the composite bar.
52
*/
53
getPinnedCompositeIds(): string[];
54
55
/**
56
* Returns if badges are enabled for that specified composite.
57
* @param compositeId The id of the composite to check
58
*/
59
areBadgesEnabled(compositeId: string): boolean;
60
61
/**
62
* Toggles whether or not badges are shown on that particular composite.
63
* @param compositeId The composite to toggle badge enablement for
64
*/
65
toggleBadgeEnablement(compositeId: string): void;
66
67
/**
68
* Reorder composite ordering by moving a composite to the location of another composite.
69
*/
70
move(compositeId: string, tocompositeId: string): void;
71
}
72
73
export interface ICompositeBarActionItem {
74
id: string;
75
name: string;
76
keybindingId?: string;
77
classNames?: string[];
78
iconUrl?: URI;
79
}
80
81
export class CompositeBarAction extends Action {
82
83
private readonly _onDidChangeCompositeBarActionItem = this._register(new Emitter<CompositeBarAction>());
84
readonly onDidChangeCompositeBarActionItem = this._onDidChangeCompositeBarActionItem.event;
85
86
private readonly _onDidChangeActivity = this._register(new Emitter<IActivity[]>());
87
readonly onDidChangeActivity = this._onDidChangeActivity.event;
88
89
private _activities: IActivity[] = [];
90
91
constructor(private item: ICompositeBarActionItem) {
92
super(item.id, item.name, item.classNames?.join(' '), true);
93
}
94
95
get compositeBarActionItem(): ICompositeBarActionItem {
96
return this.item;
97
}
98
99
set compositeBarActionItem(item: ICompositeBarActionItem) {
100
this._label = item.name;
101
this.item = item;
102
this._onDidChangeCompositeBarActionItem.fire(this);
103
}
104
105
get activities(): IActivity[] {
106
return this._activities;
107
}
108
109
set activities(activities: IActivity[]) {
110
this._activities = activities;
111
this._onDidChangeActivity.fire(activities);
112
}
113
114
activate(): void {
115
if (!this.checked) {
116
this._setChecked(true);
117
}
118
}
119
120
deactivate(): void {
121
if (this.checked) {
122
this._setChecked(false);
123
}
124
}
125
126
}
127
128
export interface ICompositeBarColors {
129
readonly activeBackgroundColor?: Color;
130
readonly inactiveBackgroundColor?: Color;
131
readonly activeBorderColor?: Color;
132
readonly activeBackground?: Color;
133
readonly activeBorderBottomColor?: Color;
134
readonly activeForegroundColor?: Color;
135
readonly inactiveForegroundColor?: Color;
136
readonly badgeBackground?: Color;
137
readonly badgeForeground?: Color;
138
readonly dragAndDropBorder?: Color;
139
}
140
141
export interface IActivityHoverOptions {
142
readonly position: () => HoverPosition;
143
}
144
145
export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptions {
146
readonly icon?: boolean;
147
readonly colors: (theme: IColorTheme) => ICompositeBarColors;
148
149
readonly hoverOptions: IActivityHoverOptions;
150
readonly hasPopup?: boolean;
151
readonly compact?: boolean;
152
}
153
154
export class CompositeBarActionViewItem extends BaseActionViewItem {
155
156
protected container!: HTMLElement;
157
protected label!: HTMLElement;
158
protected badge!: HTMLElement;
159
protected override readonly options: ICompositeBarActionViewItemOptions;
160
161
private badgeContent: HTMLElement | undefined;
162
private readonly badgeDisposable = this._register(new MutableDisposable<DisposableStore>());
163
private mouseUpTimeout: Timeout | undefined;
164
private keybindingLabel: string | undefined | null;
165
166
constructor(
167
action: CompositeBarAction,
168
options: ICompositeBarActionViewItemOptions,
169
private readonly badgesEnabled: (compositeId: string) => boolean,
170
@IThemeService protected readonly themeService: IThemeService,
171
@IHoverService private readonly hoverService: IHoverService,
172
@IConfigurationService protected readonly configurationService: IConfigurationService,
173
@IKeybindingService protected readonly keybindingService: IKeybindingService,
174
) {
175
super(null, action, options);
176
177
this.options = options;
178
179
this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this));
180
this._register(action.onDidChangeCompositeBarActionItem(() => this.update()));
181
this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle()));
182
this._register(action.onDidChangeActivity(() => this.updateActivity()));
183
}
184
185
protected get compositeBarActionItem(): ICompositeBarActionItem {
186
return (this._action as CompositeBarAction).compositeBarActionItem;
187
}
188
189
protected updateStyles(): void {
190
const theme = this.themeService.getColorTheme();
191
const colors = this.options.colors(theme);
192
193
if (this.label) {
194
if (this.options.icon) {
195
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
196
if (this.compositeBarActionItem.iconUrl) {
197
// Apply background color to activity bar item provided with iconUrls
198
this.label.style.backgroundColor = foreground ? foreground.toString() : '';
199
this.label.style.color = '';
200
} else {
201
// Apply foreground color to activity bar items provided with codicons
202
this.label.style.color = foreground ? foreground.toString() : '';
203
this.label.style.backgroundColor = '';
204
}
205
} else {
206
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
207
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
208
this.label.style.color = foreground ? foreground.toString() : '';
209
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
210
}
211
212
this.container.style.setProperty('--insert-border-color', colors.dragAndDropBorder ? colors.dragAndDropBorder.toString() : '');
213
}
214
215
// Badge
216
if (this.badgeContent) {
217
const badgeStyles = this.getActivities()[0]?.badge.getColors(theme);
218
const badgeFg = badgeStyles?.badgeForeground ?? colors.badgeForeground ?? theme.getColor(badgeForeground);
219
const badgeBg = badgeStyles?.badgeBackground ?? colors.badgeBackground ?? theme.getColor(badgeBackground);
220
const contrastBorderColor = badgeStyles?.badgeBorder ?? theme.getColor(contrastBorder);
221
222
this.badgeContent.style.color = badgeFg ? badgeFg.toString() : '';
223
this.badgeContent.style.backgroundColor = badgeBg ? badgeBg.toString() : '';
224
225
this.badgeContent.style.borderStyle = contrastBorderColor && !this.options.compact ? 'solid' : '';
226
this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : '';
227
this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : '';
228
}
229
}
230
231
override render(container: HTMLElement): void {
232
super.render(container);
233
234
this.container = container;
235
if (this.options.icon) {
236
this.container.classList.add('icon');
237
}
238
239
if (this.options.hasPopup) {
240
this.container.setAttribute('role', 'button');
241
this.container.setAttribute('aria-haspopup', 'true');
242
} else {
243
this.container.setAttribute('role', 'tab');
244
}
245
246
// Try hard to prevent keyboard only focus feedback when using mouse
247
this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, () => {
248
this.container.classList.add('clicked');
249
}));
250
251
this._register(addDisposableListener(this.container, EventType.MOUSE_UP, () => {
252
if (this.mouseUpTimeout) {
253
clearTimeout(this.mouseUpTimeout);
254
}
255
256
this.mouseUpTimeout = setTimeout(() => {
257
this.container.classList.remove('clicked');
258
}, 800); // delayed to prevent focus feedback from showing on mouse up
259
}));
260
261
this._register(this.hoverService.setupDelayedHover(this.container, () => ({
262
content: this.computeTitle(),
263
position: {
264
hoverPosition: this.options.hoverOptions.position(),
265
},
266
persistence: {
267
hideOnKeyDown: true,
268
},
269
appearance: {
270
showPointer: true,
271
compact: true,
272
}
273
}), { groupId: 'composite-bar-actions' }));
274
275
// Label
276
this.label = append(container, $('a'));
277
278
// Badge
279
this.badge = append(container, $('.badge'));
280
this.badgeContent = append(this.badge, $('.badge-content'));
281
282
// pane composite bar active border + background
283
append(container, $('.active-item-indicator'));
284
285
hide(this.badge);
286
287
this.update();
288
this.updateStyles();
289
this.updateTitle();
290
}
291
292
private onThemeChange(theme: IColorTheme): void {
293
this.updateStyles();
294
}
295
296
protected update(): void {
297
this.updateLabel();
298
this.updateActivity();
299
this.updateTitle();
300
this.updateStyles();
301
}
302
303
private getActivities(): IActivity[] {
304
if (this._action instanceof CompositeBarAction) {
305
return this._action.activities;
306
}
307
return [];
308
}
309
310
protected updateActivity(): void {
311
if (!this.badge || !this.badgeContent || !(this._action instanceof CompositeBarAction)) {
312
return;
313
}
314
315
const { badges, type } = this.getVisibleBadges(this.getActivities());
316
317
this.badgeDisposable.value = new DisposableStore();
318
319
clearNode(this.badgeContent);
320
hide(this.badge);
321
322
const shouldRenderBadges = this.badgesEnabled(this.compositeBarActionItem.id);
323
324
if (badges.length > 0 && shouldRenderBadges) {
325
326
const classes: string[] = [];
327
328
if (this.options.compact) {
329
classes.push('compact');
330
}
331
332
// Progress
333
if (type === 'progress') {
334
show(this.badge);
335
classes.push('progress-badge');
336
}
337
338
// Number
339
else if (type === 'number') {
340
const total = badges.reduce((r, b) => r + (b instanceof NumberBadge ? b.number : 0), 0);
341
if (total > 0) {
342
let badgeNumber = total.toString();
343
if (total > 999) {
344
const noOfThousands = total / 1000;
345
const floor = Math.floor(noOfThousands);
346
badgeNumber = noOfThousands > floor ? `${floor}K+` : `${noOfThousands}K`;
347
}
348
if (this.options.compact && badgeNumber.length >= 3) {
349
classes.push('compact-content');
350
}
351
this.badgeContent.textContent = badgeNumber;
352
show(this.badge);
353
}
354
}
355
356
// Icon
357
else if (type === 'icon') {
358
classes.push('icon-badge');
359
const badgeContentClassess = ['icon-overlay', ...ThemeIcon.asClassNameArray((badges[0] as IconBadge).icon)];
360
this.badgeContent.classList.add(...badgeContentClassess);
361
this.badgeDisposable.value.add(toDisposable(() => this.badgeContent?.classList.remove(...badgeContentClassess)));
362
show(this.badge);
363
}
364
365
if (classes.length) {
366
this.badge.classList.add(...classes);
367
this.badgeDisposable.value.add(toDisposable(() => this.badge.classList.remove(...classes)));
368
}
369
370
}
371
372
this.updateTitle();
373
this.updateStyles();
374
}
375
376
private getVisibleBadges(activities: IActivity[]): { badges: IBadge[]; type: 'progress' | 'icon' | 'number' | undefined } {
377
const progressBadges = activities.filter(activity => activity.badge instanceof ProgressBadge).map(activity => activity.badge);
378
if (progressBadges.length > 0) {
379
return { badges: progressBadges, type: 'progress' };
380
}
381
382
const iconBadges = activities.filter(activity => activity.badge instanceof IconBadge).map(activity => activity.badge);
383
if (iconBadges.length > 0) {
384
return { badges: iconBadges, type: 'icon' };
385
}
386
387
const numberBadges = activities.filter(activity => activity.badge instanceof NumberBadge).map(activity => activity.badge);
388
if (numberBadges.length > 0) {
389
return { badges: numberBadges, type: 'number' };
390
}
391
392
return { badges: [], type: undefined };
393
}
394
395
protected override updateLabel(): void {
396
this.label.className = 'action-label';
397
398
if (this.compositeBarActionItem.classNames) {
399
this.label.classList.add(...this.compositeBarActionItem.classNames);
400
}
401
402
if (!this.options.icon) {
403
this.label.textContent = this.action.label;
404
}
405
}
406
407
private updateTitle(): void {
408
const title = this.computeTitle();
409
[this.label, this.badge, this.container].forEach(element => {
410
if (element) {
411
element.setAttribute('aria-label', title);
412
element.setAttribute('title', '');
413
element.removeAttribute('title');
414
}
415
});
416
}
417
418
protected computeTitle(): string {
419
this.keybindingLabel = this.computeKeybindingLabel();
420
let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.compositeBarActionItem.name, this.keybindingLabel) : this.compositeBarActionItem.name;
421
422
const badges = this.getVisibleBadges((this.action as CompositeBarAction).activities).badges;
423
for (const badge of badges) {
424
const description = badge.getDescription();
425
if (!description) {
426
continue;
427
}
428
title = `${title} - ${badge.getDescription()}`;
429
}
430
431
return title;
432
}
433
434
private computeKeybindingLabel(): string | undefined | null {
435
const keybinding = this.compositeBarActionItem.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeBarActionItem.keybindingId) : null;
436
437
return keybinding?.getLabel();
438
}
439
440
override dispose(): void {
441
super.dispose();
442
443
if (this.mouseUpTimeout) {
444
clearTimeout(this.mouseUpTimeout);
445
}
446
447
this.badge.remove();
448
}
449
}
450
451
export class CompositeOverflowActivityAction extends CompositeBarAction {
452
453
constructor(
454
private showMenu: () => void
455
) {
456
super({
457
id: 'additionalComposites.action',
458
name: localize('additionalViews', "Additional Views"),
459
classNames: ThemeIcon.asClassNameArray(Codicon.more)
460
});
461
}
462
463
override async run(): Promise<void> {
464
this.showMenu();
465
}
466
}
467
468
export class CompositeOverflowActivityActionViewItem extends CompositeBarActionViewItem {
469
470
constructor(
471
action: CompositeBarAction,
472
private getOverflowingComposites: () => { id: string; name?: string }[],
473
private getActiveCompositeId: () => string | undefined,
474
private getBadge: (compositeId: string) => IBadge,
475
private getCompositeOpenAction: (compositeId: string) => IAction,
476
colors: (theme: IColorTheme) => ICompositeBarColors,
477
hoverOptions: IActivityHoverOptions,
478
@IContextMenuService private readonly contextMenuService: IContextMenuService,
479
@IThemeService themeService: IThemeService,
480
@IHoverService hoverService: IHoverService,
481
@IConfigurationService configurationService: IConfigurationService,
482
@IKeybindingService keybindingService: IKeybindingService,
483
) {
484
super(action, { icon: true, colors, hasPopup: true, hoverOptions }, () => true, themeService, hoverService, configurationService, keybindingService);
485
}
486
487
showMenu(): void {
488
this.contextMenuService.showContextMenu({
489
getAnchor: () => this.container,
490
getActions: () => this.getActions(),
491
getCheckedActionsRepresentation: () => 'radio',
492
});
493
}
494
495
private getActions(): IAction[] {
496
return this.getOverflowingComposites().map(composite => {
497
const action = this.getCompositeOpenAction(composite.id);
498
action.checked = this.getActiveCompositeId() === action.id;
499
500
const badge = this.getBadge(composite.id);
501
let suffix: string | number | undefined;
502
if (badge instanceof NumberBadge) {
503
suffix = badge.number;
504
}
505
506
if (suffix) {
507
action.label = localize('numberBadge', "{0} ({1})", composite.name, suffix);
508
} else {
509
action.label = composite.name || '';
510
}
511
512
return action;
513
});
514
}
515
}
516
517
export class CompositeActionViewItem extends CompositeBarActionViewItem {
518
519
constructor(
520
options: ICompositeBarActionViewItemOptions,
521
compositeActivityAction: CompositeBarAction,
522
private readonly toggleCompositePinnedAction: IAction,
523
private readonly toggleCompositeBadgeAction: IAction,
524
private readonly compositeContextMenuActionsProvider: (compositeId: string) => IAction[],
525
private readonly contextMenuActionsProvider: () => IAction[],
526
private readonly dndHandler: ICompositeDragAndDrop,
527
private readonly compositeBar: ICompositeBar,
528
@IContextMenuService private readonly contextMenuService: IContextMenuService,
529
@IKeybindingService keybindingService: IKeybindingService,
530
@IInstantiationService instantiationService: IInstantiationService,
531
@IThemeService themeService: IThemeService,
532
@IHoverService hoverService: IHoverService,
533
@IConfigurationService configurationService: IConfigurationService,
534
@ICommandService private readonly commandService: ICommandService
535
) {
536
super(
537
compositeActivityAction,
538
options,
539
compositeBar.areBadgesEnabled.bind(compositeBar),
540
themeService,
541
hoverService,
542
configurationService,
543
keybindingService
544
);
545
}
546
547
override render(container: HTMLElement): void {
548
super.render(container);
549
550
this.updateChecked();
551
this.updateEnabled();
552
553
this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, e => {
554
EventHelper.stop(e, true);
555
556
this.showContextMenu(container);
557
}));
558
559
// Allow to drag
560
let insertDropBefore: Before2D | undefined = undefined;
561
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, () => { return { type: 'composite', id: this.compositeBarActionItem.id }; }, {
562
onDragOver: e => {
563
const isValidMove = e.dragAndDropData.getData().id !== this.compositeBarActionItem.id && this.dndHandler.onDragOver(e.dragAndDropData, this.compositeBarActionItem.id, e.eventData);
564
toggleDropEffect(e.eventData.dataTransfer, 'move', isValidMove);
565
insertDropBefore = this.updateFromDragging(container, isValidMove, e.eventData);
566
},
567
onDragLeave: e => {
568
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
569
},
570
onDragEnd: e => {
571
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
572
},
573
onDrop: e => {
574
EventHelper.stop(e.eventData, true);
575
this.dndHandler.drop(e.dragAndDropData, this.compositeBarActionItem.id, e.eventData, insertDropBefore);
576
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
577
},
578
onDragStart: e => {
579
if (e.dragAndDropData.getData().id !== this.compositeBarActionItem.id) {
580
return;
581
}
582
583
if (e.eventData.dataTransfer) {
584
e.eventData.dataTransfer.effectAllowed = 'move';
585
}
586
587
this.blur(); // Remove focus indicator when dragging
588
}
589
}));
590
591
// Activate on drag over to reveal targets
592
[this.badge, this.label].forEach(element => this._register(new DelayedDragHandler(element, () => {
593
if (!this.action.checked) {
594
this.action.run();
595
}
596
})));
597
598
this.updateStyles();
599
}
600
601
private updateFromDragging(element: HTMLElement, showFeedback: boolean, event: DragEvent): Before2D | undefined {
602
const rect = element.getBoundingClientRect();
603
const posX = event.clientX;
604
const posY = event.clientY;
605
const height = rect.bottom - rect.top;
606
const width = rect.right - rect.left;
607
608
const forceTop = posY <= rect.top + height * 0.4;
609
const forceBottom = posY > rect.bottom - height * 0.4;
610
const preferTop = posY <= rect.top + height * 0.5;
611
612
const forceLeft = posX <= rect.left + width * 0.4;
613
const forceRight = posX > rect.right - width * 0.4;
614
const preferLeft = posX <= rect.left + width * 0.5;
615
616
const classes = element.classList;
617
const lastClasses = {
618
vertical: classes.contains('top') ? 'top' : (classes.contains('bottom') ? 'bottom' : undefined),
619
horizontal: classes.contains('left') ? 'left' : (classes.contains('right') ? 'right' : undefined)
620
};
621
622
const top = forceTop || (preferTop && !lastClasses.vertical) || (!forceBottom && lastClasses.vertical === 'top');
623
const bottom = forceBottom || (!preferTop && !lastClasses.vertical) || (!forceTop && lastClasses.vertical === 'bottom');
624
const left = forceLeft || (preferLeft && !lastClasses.horizontal) || (!forceRight && lastClasses.horizontal === 'left');
625
const right = forceRight || (!preferLeft && !lastClasses.horizontal) || (!forceLeft && lastClasses.horizontal === 'right');
626
627
element.classList.toggle('top', showFeedback && top);
628
element.classList.toggle('bottom', showFeedback && bottom);
629
element.classList.toggle('left', showFeedback && left);
630
element.classList.toggle('right', showFeedback && right);
631
632
if (!showFeedback) {
633
return undefined;
634
}
635
636
return { verticallyBefore: top, horizontallyBefore: left };
637
}
638
639
private showContextMenu(container: HTMLElement): void {
640
const actions: IAction[] = [];
641
642
if (this.compositeBarActionItem.keybindingId) {
643
actions.push(createConfigureKeybindingAction(this.commandService, this.keybindingService, this.compositeBarActionItem.keybindingId));
644
}
645
646
actions.push(this.toggleCompositePinnedAction, this.toggleCompositeBadgeAction);
647
648
const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.compositeBarActionItem.id);
649
if (compositeContextMenuActions.length) {
650
actions.push(...compositeContextMenuActions);
651
}
652
653
const isPinned = this.compositeBar.isPinned(this.compositeBarActionItem.id);
654
if (isPinned) {
655
this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.compositeBarActionItem.name);
656
this.toggleCompositePinnedAction.checked = false;
657
this.toggleCompositePinnedAction.enabled = this.compositeBar.getPinnedCompositeIds().length > 1;
658
} else {
659
this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.compositeBarActionItem.name);
660
this.toggleCompositePinnedAction.enabled = true;
661
}
662
663
const isBadgeEnabled = this.compositeBar.areBadgesEnabled(this.compositeBarActionItem.id);
664
if (isBadgeEnabled) {
665
this.toggleCompositeBadgeAction.label = localize('hideBadge', "Hide Badge");
666
} else {
667
this.toggleCompositeBadgeAction.label = localize('showBadge', "Show Badge");
668
}
669
670
const otherActions = this.contextMenuActionsProvider();
671
if (otherActions.length) {
672
actions.push(new Separator());
673
actions.push(...otherActions);
674
}
675
676
const elementPosition = getDomNodePagePosition(container);
677
const anchor = {
678
x: Math.floor(elementPosition.left + (elementPosition.width / 2)),
679
y: elementPosition.top + elementPosition.height
680
};
681
682
this.contextMenuService.showContextMenu({
683
getAnchor: () => anchor,
684
getActions: () => actions,
685
getActionsContext: () => this.compositeBarActionItem.id
686
});
687
}
688
689
protected override updateChecked(): void {
690
if (this.action.checked) {
691
this.container.classList.add('checked');
692
this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title);
693
this.container.setAttribute('aria-expanded', 'true');
694
this.container.setAttribute('aria-selected', 'true');
695
} else {
696
this.container.classList.remove('checked');
697
this.container.setAttribute('aria-label', this.getTooltip() ?? this.container.title);
698
this.container.setAttribute('aria-expanded', 'false');
699
this.container.setAttribute('aria-selected', 'false');
700
}
701
702
this.updateStyles();
703
}
704
705
protected override updateEnabled(): void {
706
if (!this.element) {
707
return;
708
}
709
710
if (this.action.enabled) {
711
this.element.classList.remove('disabled');
712
} else {
713
this.element.classList.add('disabled');
714
}
715
}
716
717
override dispose(): void {
718
super.dispose();
719
720
this.label.remove();
721
}
722
}
723
724
export class ToggleCompositePinnedAction extends Action {
725
726
constructor(
727
private activity: ICompositeBarActionItem | undefined,
728
private compositeBar: ICompositeBar
729
) {
730
super('show.toggleCompositePinned', activity ? activity.name : localize('toggle', "Toggle View Pinned"));
731
732
this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id);
733
}
734
735
override async run(context: string): Promise<void> {
736
const id = this.activity ? this.activity.id : context;
737
738
if (this.compositeBar.isPinned(id)) {
739
this.compositeBar.unpin(id);
740
} else {
741
this.compositeBar.pin(id);
742
}
743
}
744
}
745
746
export class ToggleCompositeBadgeAction extends Action {
747
constructor(
748
private compositeBarActionItem: ICompositeBarActionItem | undefined,
749
private compositeBar: ICompositeBar
750
) {
751
super('show.toggleCompositeBadge', compositeBarActionItem ? compositeBarActionItem.name : localize('toggleBadge', "Toggle View Badge"));
752
753
this.checked = false;
754
}
755
756
override async run(context: string): Promise<void> {
757
const id = this.compositeBarActionItem ? this.compositeBarActionItem.id : context;
758
this.compositeBar.toggleBadgeEnablement(id);
759
}
760
}
761
762
export class SwitchCompositeViewAction extends Action2 {
763
constructor(
764
desc: Readonly<IAction2Options>,
765
private readonly location: ViewContainerLocation,
766
private readonly offset: number
767
) {
768
super(desc);
769
}
770
771
async run(accessor: ServicesAccessor): Promise<void> {
772
const paneCompositeService = accessor.get(IPaneCompositePartService);
773
774
const activeComposite = paneCompositeService.getActivePaneComposite(this.location);
775
if (!activeComposite) {
776
return;
777
}
778
779
let targetCompositeId: string | undefined;
780
781
const visibleCompositeIds = paneCompositeService.getVisiblePaneCompositeIds(this.location);
782
for (let i = 0; i < visibleCompositeIds.length; i++) {
783
if (visibleCompositeIds[i] === activeComposite.getId()) {
784
targetCompositeId = visibleCompositeIds[(i + visibleCompositeIds.length + this.offset) % visibleCompositeIds.length];
785
break;
786
}
787
}
788
789
if (typeof targetCompositeId !== 'undefined') {
790
await paneCompositeService.openPaneComposite(targetCompositeId, this.location, true);
791
}
792
}
793
}
794
795