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