Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/globalCompositeBar.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 { ActionBar, ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';
8
import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from '../../common/activity.js';
9
import { IActivityService } from '../../services/activity/common/activity.js';
10
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
11
import { DisposableStore, Disposable } from '../../../base/common/lifecycle.js';
12
import { IColorTheme, IThemeService } from '../../../platform/theme/common/themeService.js';
13
import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js';
14
import { IExtensionService } from '../../services/extensions/common/extensions.js';
15
import { CompositeBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from './compositeBarActions.js';
16
import { Codicon } from '../../../base/common/codicons.js';
17
import { ThemeIcon } from '../../../base/common/themables.js';
18
import { registerIcon } from '../../../platform/theme/common/iconRegistry.js';
19
import { Action, IAction, Separator, SubmenuAction, toAction } from '../../../base/common/actions.js';
20
import { IMenu, IMenuService, MenuId } from '../../../platform/actions/common/actions.js';
21
import { addDisposableListener, EventType, append, clearNode, hide, show, EventHelper, $, runWhenWindowIdle, getWindow } from '../../../base/browser/dom.js';
22
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
23
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
24
import { EventType as TouchEventType, GestureEvent } from '../../../base/browser/touch.js';
25
import { AnchorAlignment, AnchorAxisAlignment } from '../../../base/browser/ui/contextview/contextview.js';
26
import { Lazy } from '../../../base/common/lazy.js';
27
import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js';
28
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
29
import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
30
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
31
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
32
import { ILogService } from '../../../platform/log/common/log.js';
33
import { IProductService } from '../../../platform/product/common/productService.js';
34
import { ISecretStorageService } from '../../../platform/secrets/common/secrets.js';
35
import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from '../../services/authentication/browser/authenticationService.js';
36
import { AuthenticationSessionAccount, IAuthenticationService, INTERNAL_AUTH_PROVIDER_PREFIX } from '../../services/authentication/common/authentication.js';
37
import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';
38
import { IHoverService } from '../../../platform/hover/browser/hover.js';
39
import { ILifecycleService, LifecyclePhase } from '../../services/lifecycle/common/lifecycle.js';
40
import { IUserDataProfileService } from '../../services/userDataProfile/common/userDataProfile.js';
41
import { DEFAULT_ICON } from '../../services/userDataProfile/common/userDataProfileIcons.js';
42
import { isString } from '../../../base/common/types.js';
43
import { KeyCode } from '../../../base/common/keyCodes.js';
44
import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from '../../common/theme.js';
45
import { IBaseActionViewItemOptions } from '../../../base/browser/ui/actionbar/actionViewItems.js';
46
import { ICommandService } from '../../../platform/commands/common/commands.js';
47
48
export class GlobalCompositeBar extends Disposable {
49
50
private static readonly ACCOUNTS_ACTION_INDEX = 0;
51
static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar."));
52
53
readonly element: HTMLElement;
54
55
private readonly globalActivityAction = this._register(new Action(GLOBAL_ACTIVITY_ID));
56
private readonly accountAction = this._register(new Action(ACCOUNTS_ACTIVITY_ID));
57
private readonly globalActivityActionBar: ActionBar;
58
59
constructor(
60
private readonly contextMenuActionsProvider: () => IAction[],
61
private readonly colors: (theme: IColorTheme) => ICompositeBarColors,
62
private readonly activityHoverOptions: IActivityHoverOptions,
63
@IConfigurationService configurationService: IConfigurationService,
64
@IInstantiationService private readonly instantiationService: IInstantiationService,
65
@IStorageService private readonly storageService: IStorageService,
66
@IExtensionService private readonly extensionService: IExtensionService,
67
) {
68
super();
69
70
this.element = $('div');
71
const contextMenuAlignmentOptions = () => ({
72
anchorAlignment: configurationService.getValue('workbench.sideBar.location') === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT,
73
anchorAxisAlignment: AnchorAxisAlignment.HORIZONTAL
74
});
75
this.globalActivityActionBar = this._register(new ActionBar(this.element, {
76
actionViewItemProvider: (action, options) => {
77
if (action.id === GLOBAL_ACTIVITY_ID) {
78
return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { ...options, colors: this.colors, hoverOptions: this.activityHoverOptions }, contextMenuAlignmentOptions);
79
}
80
81
if (action.id === ACCOUNTS_ACTIVITY_ID) {
82
return this.instantiationService.createInstance(AccountsActivityActionViewItem,
83
this.contextMenuActionsProvider,
84
{
85
...options,
86
colors: this.colors,
87
hoverOptions: this.activityHoverOptions
88
},
89
contextMenuAlignmentOptions,
90
(actions: IAction[]) => {
91
actions.unshift(...[
92
toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => setAccountsActionVisible(storageService, false) }),
93
new Separator()
94
]);
95
});
96
}
97
98
throw new Error(`No view item for action '${action.id}'`);
99
},
100
orientation: ActionsOrientation.VERTICAL,
101
ariaLabel: localize('manage', "Manage"),
102
preventLoopNavigation: true
103
}));
104
105
if (this.accountsVisibilityPreference) {
106
this.globalActivityActionBar.push(this.accountAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX });
107
}
108
109
this.globalActivityActionBar.push(this.globalActivityAction);
110
111
this.registerListeners();
112
}
113
114
private registerListeners(): void {
115
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
116
if (!this._store.isDisposed) {
117
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, this._store)(() => this.toggleAccountsActivity()));
118
}
119
});
120
}
121
122
create(parent: HTMLElement): void {
123
parent.appendChild(this.element);
124
}
125
126
focus(): void {
127
this.globalActivityActionBar.focus(true);
128
}
129
130
size(): number {
131
return this.globalActivityActionBar.viewItems.length;
132
}
133
134
getContextMenuActions(): IAction[] {
135
return [toAction({ id: 'toggleAccountsVisibility', label: localize('accounts', "Accounts"), checked: this.accountsVisibilityPreference, run: () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference })];
136
}
137
138
private toggleAccountsActivity() {
139
if (this.globalActivityActionBar.length() === 2 && this.accountsVisibilityPreference) {
140
return;
141
}
142
if (this.globalActivityActionBar.length() === 2) {
143
this.globalActivityActionBar.pull(GlobalCompositeBar.ACCOUNTS_ACTION_INDEX);
144
} else {
145
this.globalActivityActionBar.push(this.accountAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX });
146
}
147
}
148
149
private get accountsVisibilityPreference(): boolean {
150
return isAccountsActionVisible(this.storageService);
151
}
152
153
private set accountsVisibilityPreference(value: boolean) {
154
setAccountsActionVisible(this.storageService, value);
155
}
156
}
157
158
abstract class AbstractGlobalActivityActionViewItem extends CompositeBarActionViewItem {
159
160
constructor(
161
private readonly menuId: MenuId,
162
action: CompositeBarAction,
163
options: ICompositeBarActionViewItemOptions,
164
private readonly contextMenuActionsProvider: () => IAction[],
165
private readonly contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined,
166
@IThemeService themeService: IThemeService,
167
@IHoverService hoverService: IHoverService,
168
@IMenuService private readonly menuService: IMenuService,
169
@IContextMenuService private readonly contextMenuService: IContextMenuService,
170
@IContextKeyService private readonly contextKeyService: IContextKeyService,
171
@IConfigurationService configurationService: IConfigurationService,
172
@IKeybindingService keybindingService: IKeybindingService,
173
@IActivityService private readonly activityService: IActivityService,
174
) {
175
super(action, { draggable: false, icon: true, hasPopup: true, ...options }, () => true, themeService, hoverService, configurationService, keybindingService);
176
177
this.updateItemActivity();
178
this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => {
179
if (isString(viewContainerOrAction) && viewContainerOrAction === this.compositeBarActionItem.id) {
180
this.updateItemActivity();
181
}
182
}));
183
}
184
185
private updateItemActivity(): void {
186
(this.action as CompositeBarAction).activities = this.activityService.getActivity(this.compositeBarActionItem.id);
187
}
188
189
override render(container: HTMLElement): void {
190
super.render(container);
191
192
this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, async (e: MouseEvent) => {
193
EventHelper.stop(e, true);
194
const isLeftClick = e?.button !== 2;
195
// Left-click run
196
if (isLeftClick) {
197
this.run();
198
}
199
}));
200
201
// The rest of the activity bar uses context menu event for the context menu, so we match this
202
this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, async (e: MouseEvent) => {
203
// Let the item decide on the context menu instead of the toolbar
204
e.stopPropagation();
205
206
const disposables = new DisposableStore();
207
const actions = await this.resolveContextMenuActions(disposables);
208
209
const event = new StandardMouseEvent(getWindow(this.container), e);
210
211
this.contextMenuService.showContextMenu({
212
getAnchor: () => event,
213
getActions: () => actions,
214
onHide: () => disposables.dispose()
215
});
216
}));
217
218
this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => {
219
const event = new StandardKeyboardEvent(e);
220
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
221
EventHelper.stop(e, true);
222
this.run();
223
}
224
}));
225
226
this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => {
227
EventHelper.stop(e, true);
228
this.run();
229
}));
230
}
231
232
protected async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
233
return this.contextMenuActionsProvider();
234
}
235
236
private async run(): Promise<void> {
237
const disposables = new DisposableStore();
238
const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService));
239
const actions = await this.resolveMainMenuActions(menu, disposables);
240
const { anchorAlignment, anchorAxisAlignment } = this.contextMenuAlignmentOptions() ?? { anchorAlignment: undefined, anchorAxisAlignment: undefined };
241
242
this.contextMenuService.showContextMenu({
243
getAnchor: () => this.label,
244
anchorAlignment,
245
anchorAxisAlignment,
246
getActions: () => actions,
247
onHide: () => disposables.dispose(),
248
menuActionOptions: { renderShortTitle: true },
249
});
250
251
}
252
253
protected async resolveMainMenuActions(menu: IMenu, _disposable: DisposableStore): Promise<IAction[]> {
254
return getActionBarActions(menu.getActions({ renderShortTitle: true })).secondary;
255
}
256
}
257
258
export class AccountsActivityActionViewItem extends AbstractGlobalActivityActionViewItem {
259
260
static readonly ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts';
261
262
private readonly groupedAccounts: Map<string, (AuthenticationSessionAccount & { canSignOut: boolean })[]> = new Map();
263
private readonly problematicProviders: Set<string> = new Set();
264
265
private initialized = false;
266
private sessionFromEmbedder = new Lazy<Promise<AuthenticationSessionInfo | undefined>>(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService));
267
268
constructor(
269
contextMenuActionsProvider: () => IAction[],
270
options: ICompositeBarActionViewItemOptions,
271
contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined,
272
private readonly fillContextMenuActions: (actions: IAction[]) => void,
273
@IThemeService themeService: IThemeService,
274
@ILifecycleService private readonly lifecycleService: ILifecycleService,
275
@IHoverService hoverService: IHoverService,
276
@IContextMenuService contextMenuService: IContextMenuService,
277
@IMenuService menuService: IMenuService,
278
@IContextKeyService contextKeyService: IContextKeyService,
279
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
280
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
281
@IProductService private readonly productService: IProductService,
282
@IConfigurationService configurationService: IConfigurationService,
283
@IKeybindingService keybindingService: IKeybindingService,
284
@ISecretStorageService private readonly secretStorageService: ISecretStorageService,
285
@ILogService private readonly logService: ILogService,
286
@IActivityService activityService: IActivityService,
287
@IInstantiationService instantiationService: IInstantiationService,
288
@ICommandService private readonly commandService: ICommandService
289
) {
290
const action = instantiationService.createInstance(CompositeBarAction, {
291
id: ACCOUNTS_ACTIVITY_ID,
292
name: localize('accounts', "Accounts"),
293
classNames: ThemeIcon.asClassNameArray(GlobalCompositeBar.ACCOUNTS_ICON)
294
});
295
super(MenuId.AccountsContext, action, options, contextMenuActionsProvider, contextMenuAlignmentOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService);
296
this._register(action);
297
this.registerListeners();
298
this.initialize();
299
}
300
301
private registerListeners(): void {
302
this._register(this.authenticationService.onDidRegisterAuthenticationProvider(async (e) => {
303
await this.addAccountsFromProvider(e.id);
304
}));
305
306
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider((e) => {
307
this.groupedAccounts.delete(e.id);
308
this.problematicProviders.delete(e.id);
309
}));
310
311
this._register(this.authenticationService.onDidChangeSessions(async e => {
312
if (e.event.removed) {
313
for (const removed of e.event.removed) {
314
this.removeAccount(e.providerId, removed.account);
315
}
316
}
317
for (const changed of [...(e.event.changed ?? []), ...(e.event.added ?? [])]) {
318
try {
319
await this.addOrUpdateAccount(e.providerId, changed.account);
320
} catch (e) {
321
this.logService.error(e);
322
}
323
}
324
}));
325
}
326
327
// This function exists to ensure that the accounts are added for auth providers that had already been registered
328
// before the menu was created.
329
private async initialize(): Promise<void> {
330
// Resolving the menu doesn't need to happen immediately, so we can wait until after the workbench has been restored
331
// and only run this when the system is idle.
332
await this.lifecycleService.when(LifecyclePhase.Restored);
333
if (this._store.isDisposed) {
334
return;
335
}
336
const disposable = this._register(runWhenWindowIdle(getWindow(this.element), async () => {
337
await this.doInitialize();
338
disposable.dispose();
339
}));
340
}
341
342
private async doInitialize(): Promise<void> {
343
const providerIds = this.authenticationService.getProviderIds();
344
const results = await Promise.allSettled(providerIds.map(providerId => this.addAccountsFromProvider(providerId)));
345
346
// Log any errors that occurred while initializing. We try to be best effort here to show the most amount of accounts
347
for (const result of results) {
348
if (result.status === 'rejected') {
349
this.logService.error(result.reason);
350
}
351
}
352
353
this.initialized = true;
354
}
355
356
//#region overrides
357
358
protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise<IAction[]> {
359
await super.resolveMainMenuActions(accountsMenu, disposables);
360
361
const providers = this.authenticationService.getProviderIds().filter(p => !p.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX));
362
const otherCommands = accountsMenu.getActions();
363
let menus: IAction[] = [];
364
365
const registeredProviders = providers.filter(providerId => !this.authenticationService.isDynamicAuthenticationProvider(providerId));
366
const dynamicProviders = providers.filter(providerId => this.authenticationService.isDynamicAuthenticationProvider(providerId));
367
368
if (!this.initialized) {
369
const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('loading', "Loading..."), undefined, false));
370
menus.push(noAccountsAvailableAction);
371
} else {
372
for (const providerId of registeredProviders) {
373
const provider = this.authenticationService.getProvider(providerId);
374
const accounts = this.groupedAccounts.get(providerId);
375
if (!accounts) {
376
if (this.problematicProviders.has(providerId)) {
377
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', provider.label), undefined, false));
378
menus.push(providerUnavailableAction);
379
// try again in the background so that if the failure was intermittent, we can resolve it on the next showing of the menu
380
try {
381
await this.addAccountsFromProvider(providerId);
382
} catch (e) {
383
this.logService.error(e);
384
}
385
}
386
continue;
387
}
388
389
const canUseMcp = !!provider.authorizationServers?.length;
390
for (const account of accounts) {
391
const manageExtensionsAction = toAction({
392
id: `configureSessions${account.label}`,
393
label: localize('manageTrustedExtensions', "Manage Trusted Extensions"),
394
enabled: true,
395
run: () => this.commandService.executeCommand('_manageTrustedExtensionsForAccount', { providerId, accountLabel: account.label })
396
});
397
398
399
const providerSubMenuActions: IAction[] = [manageExtensionsAction];
400
if (canUseMcp) {
401
const manageMCPAction = toAction({
402
id: `configureSessions${account.label}`,
403
label: localize('manageTrustedMCPServers', "Manage Trusted MCP Servers"),
404
enabled: true,
405
run: () => this.commandService.executeCommand('_manageTrustedMCPServersForAccount', { providerId, accountLabel: account.label })
406
});
407
providerSubMenuActions.push(manageMCPAction);
408
}
409
if (account.canSignOut) {
410
providerSubMenuActions.push(toAction({
411
id: 'signOut',
412
label: localize('signOut', "Sign Out"),
413
enabled: true,
414
run: () => this.commandService.executeCommand('_signOutOfAccount', { providerId, accountLabel: account.label })
415
}));
416
}
417
418
const providerSubMenu = new SubmenuAction('activitybar.submenu', `${account.label} (${provider.label})`, providerSubMenuActions);
419
menus.push(providerSubMenu);
420
}
421
}
422
423
if (dynamicProviders.length && registeredProviders.length) {
424
menus.push(new Separator());
425
}
426
427
for (const providerId of dynamicProviders) {
428
const provider = this.authenticationService.getProvider(providerId);
429
const accounts = this.groupedAccounts.get(providerId);
430
// Provide _some_ discoverable way to manage dynamic authentication providers.
431
// This will either show up inside the account submenu or as a top-level menu item if there
432
// are no accounts.
433
const manageDynamicAuthProvidersAction = toAction({
434
id: 'manageDynamicAuthProviders',
435
label: localize('manageDynamicAuthProviders', "Manage Dynamic Authentication Providers..."),
436
enabled: true,
437
run: () => this.commandService.executeCommand('workbench.action.removeDynamicAuthenticationProviders')
438
});
439
if (!accounts) {
440
if (this.problematicProviders.has(providerId)) {
441
const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', provider.label), undefined, false));
442
menus.push(providerUnavailableAction);
443
// try again in the background so that if the failure was intermittent, we can resolve it on the next showing of the menu
444
try {
445
await this.addAccountsFromProvider(providerId);
446
} catch (e) {
447
this.logService.error(e);
448
}
449
}
450
menus.push(manageDynamicAuthProvidersAction);
451
continue;
452
}
453
454
for (const account of accounts) {
455
// TODO@TylerLeonhardt: Is there a nice way to bring this back?
456
// const manageExtensionsAction = toAction({
457
// id: `configureSessions${account.label}`,
458
// label: localize('manageTrustedExtensions', "Manage Trusted Extensions"),
459
// enabled: true,
460
// run: () => this.commandService.executeCommand('_manageTrustedExtensionsForAccount', { providerId, accountLabel: account.label })
461
// });
462
463
const providerSubMenuActions: IAction[] = [];
464
const manageMCPAction = toAction({
465
id: `configureSessions${account.label}`,
466
label: localize('manageTrustedMCPServers', "Manage Trusted MCP Servers"),
467
enabled: true,
468
run: () => this.commandService.executeCommand('_manageTrustedMCPServersForAccount', { providerId, accountLabel: account.label })
469
});
470
providerSubMenuActions.push(manageMCPAction);
471
providerSubMenuActions.push(manageDynamicAuthProvidersAction);
472
if (account.canSignOut) {
473
providerSubMenuActions.push(toAction({
474
id: 'signOut',
475
label: localize('signOut', "Sign Out"),
476
enabled: true,
477
run: () => this.commandService.executeCommand('_signOutOfAccount', { providerId, accountLabel: account.label })
478
}));
479
}
480
481
const providerSubMenu = new SubmenuAction('activitybar.submenu', `${account.label} (${provider.label})`, providerSubMenuActions);
482
menus.push(providerSubMenu);
483
}
484
}
485
}
486
487
if (menus.length && otherCommands.length) {
488
menus.push(new Separator());
489
}
490
491
otherCommands.forEach((group, i) => {
492
const actions = group[1];
493
menus = menus.concat(actions);
494
if (i !== otherCommands.length - 1) {
495
menus.push(new Separator());
496
}
497
});
498
499
return menus;
500
}
501
502
protected override async resolveContextMenuActions(disposables: DisposableStore): Promise<IAction[]> {
503
const actions = await super.resolveContextMenuActions(disposables);
504
this.fillContextMenuActions(actions);
505
return actions;
506
}
507
508
//#endregion
509
510
//#region groupedAccounts helpers
511
512
private async addOrUpdateAccount(providerId: string, account: AuthenticationSessionAccount): Promise<void> {
513
let accounts = this.groupedAccounts.get(providerId);
514
if (!accounts) {
515
accounts = [];
516
this.groupedAccounts.set(providerId, accounts);
517
}
518
519
const sessionFromEmbedder = await this.sessionFromEmbedder.value;
520
let canSignOut = true;
521
if (
522
sessionFromEmbedder // if we have a session from the embedder
523
&& !sessionFromEmbedder.canSignOut // and that session says we can't sign out
524
&& (await this.authenticationService.getSessions(providerId)) // and that session is associated with the account we are adding/updating
525
.some(s =>
526
s.id === sessionFromEmbedder.id
527
&& s.account.id === account.id
528
)
529
) {
530
canSignOut = false;
531
}
532
533
const existingAccount = accounts.find(a => a.label === account.label);
534
if (existingAccount) {
535
// if we have an existing account and we discover that we
536
// can't sign out of it, update the account to mark it as "can't sign out"
537
if (!canSignOut) {
538
existingAccount.canSignOut = canSignOut;
539
}
540
} else {
541
accounts.push({ ...account, canSignOut });
542
}
543
}
544
545
private removeAccount(providerId: string, account: AuthenticationSessionAccount): void {
546
const accounts = this.groupedAccounts.get(providerId);
547
if (!accounts) {
548
return;
549
}
550
551
const index = accounts.findIndex(a => a.id === account.id);
552
if (index === -1) {
553
return;
554
}
555
556
accounts.splice(index, 1);
557
if (accounts.length === 0) {
558
this.groupedAccounts.delete(providerId);
559
}
560
}
561
562
private async addAccountsFromProvider(providerId: string): Promise<void> {
563
try {
564
const sessions = await this.authenticationService.getSessions(providerId);
565
this.problematicProviders.delete(providerId);
566
567
for (const session of sessions) {
568
try {
569
await this.addOrUpdateAccount(providerId, session.account);
570
} catch (e) {
571
this.logService.error(e);
572
}
573
}
574
} catch (e) {
575
this.logService.error(e);
576
this.problematicProviders.add(providerId);
577
}
578
}
579
580
//#endregion
581
}
582
583
export class GlobalActivityActionViewItem extends AbstractGlobalActivityActionViewItem {
584
585
private profileBadge: HTMLElement | undefined;
586
private profileBadgeContent: HTMLElement | undefined;
587
588
constructor(
589
contextMenuActionsProvider: () => IAction[],
590
options: ICompositeBarActionViewItemOptions,
591
contextMenuAlignmentOptions: () => Readonly<{ anchorAlignment: AnchorAlignment; anchorAxisAlignment: AnchorAxisAlignment }> | undefined,
592
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
593
@IThemeService themeService: IThemeService,
594
@IHoverService hoverService: IHoverService,
595
@IMenuService menuService: IMenuService,
596
@IContextMenuService contextMenuService: IContextMenuService,
597
@IContextKeyService contextKeyService: IContextKeyService,
598
@IConfigurationService configurationService: IConfigurationService,
599
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
600
@IKeybindingService keybindingService: IKeybindingService,
601
@IInstantiationService instantiationService: IInstantiationService,
602
@IActivityService activityService: IActivityService,
603
) {
604
const action = instantiationService.createInstance(CompositeBarAction, {
605
id: GLOBAL_ACTIVITY_ID,
606
name: localize('manage', "Manage"),
607
classNames: ThemeIcon.asClassNameArray(userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(userDataProfileService.currentProfile.icon) : DEFAULT_ICON)
608
});
609
super(MenuId.GlobalActivity, action, options, contextMenuActionsProvider, contextMenuAlignmentOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService);
610
this._register(action);
611
this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => {
612
action.compositeBarActionItem = {
613
...action.compositeBarActionItem,
614
classNames: ThemeIcon.asClassNameArray(userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(userDataProfileService.currentProfile.icon) : DEFAULT_ICON)
615
};
616
}));
617
}
618
619
override render(container: HTMLElement): void {
620
super.render(container);
621
622
this.profileBadge = append(container, $('.profile-badge'));
623
this.profileBadgeContent = append(this.profileBadge, $('.profile-badge-content'));
624
this.updateProfileBadge();
625
}
626
627
private updateProfileBadge(): void {
628
if (!this.profileBadge || !this.profileBadgeContent) {
629
return;
630
}
631
632
clearNode(this.profileBadgeContent);
633
hide(this.profileBadge);
634
635
if (this.userDataProfileService.currentProfile.isDefault) {
636
return;
637
}
638
639
if (this.userDataProfileService.currentProfile.icon && this.userDataProfileService.currentProfile.icon !== DEFAULT_ICON.id) {
640
return;
641
}
642
643
if ((this.action as CompositeBarAction).activities.length > 0) {
644
return;
645
}
646
647
show(this.profileBadge);
648
this.profileBadgeContent.classList.add('profile-text-overlay');
649
this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase();
650
}
651
652
protected override updateActivity(): void {
653
super.updateActivity();
654
this.updateProfileBadge();
655
}
656
657
protected override computeTitle(): string {
658
return this.userDataProfileService.currentProfile.isDefault ? super.computeTitle() : localize('manage profile', "Manage {0} (Profile)", this.userDataProfileService.currentProfile.name);
659
}
660
}
661
662
export class SimpleAccountActivityActionViewItem extends AccountsActivityActionViewItem {
663
664
constructor(
665
hoverOptions: IActivityHoverOptions,
666
options: IBaseActionViewItemOptions,
667
@IThemeService themeService: IThemeService,
668
@ILifecycleService lifecycleService: ILifecycleService,
669
@IHoverService hoverService: IHoverService,
670
@IContextMenuService contextMenuService: IContextMenuService,
671
@IMenuService menuService: IMenuService,
672
@IContextKeyService contextKeyService: IContextKeyService,
673
@IAuthenticationService authenticationService: IAuthenticationService,
674
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
675
@IProductService productService: IProductService,
676
@IConfigurationService configurationService: IConfigurationService,
677
@IKeybindingService keybindingService: IKeybindingService,
678
@ISecretStorageService secretStorageService: ISecretStorageService,
679
@IStorageService storageService: IStorageService,
680
@ILogService logService: ILogService,
681
@IActivityService activityService: IActivityService,
682
@IInstantiationService instantiationService: IInstantiationService,
683
@ICommandService commandService: ICommandService
684
) {
685
super(() => simpleActivityContextMenuActions(storageService, true),
686
{
687
...options,
688
colors: theme => ({
689
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
690
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
691
}),
692
hoverOptions,
693
compact: true,
694
}, () => undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService, commandService);
695
}
696
}
697
698
export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionViewItem {
699
700
constructor(
701
hoverOptions: IActivityHoverOptions,
702
options: IBaseActionViewItemOptions,
703
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
704
@IThemeService themeService: IThemeService,
705
@IHoverService hoverService: IHoverService,
706
@IMenuService menuService: IMenuService,
707
@IContextMenuService contextMenuService: IContextMenuService,
708
@IContextKeyService contextKeyService: IContextKeyService,
709
@IConfigurationService configurationService: IConfigurationService,
710
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
711
@IKeybindingService keybindingService: IKeybindingService,
712
@IInstantiationService instantiationService: IInstantiationService,
713
@IActivityService activityService: IActivityService,
714
@IStorageService storageService: IStorageService
715
) {
716
super(() => simpleActivityContextMenuActions(storageService, false),
717
{
718
...options,
719
colors: theme => ({
720
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
721
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
722
}),
723
hoverOptions,
724
compact: true,
725
}, () => undefined, userDataProfileService, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService, instantiationService, activityService);
726
}
727
}
728
729
function simpleActivityContextMenuActions(storageService: IStorageService, isAccount: boolean): IAction[] {
730
const currentElementContextMenuActions: IAction[] = [];
731
if (isAccount) {
732
currentElementContextMenuActions.push(
733
toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => setAccountsActionVisible(storageService, false) }),
734
new Separator()
735
);
736
}
737
return [
738
...currentElementContextMenuActions,
739
toAction({ id: 'toggle.hideAccounts', label: localize('accounts', "Accounts"), checked: isAccountsActionVisible(storageService), run: () => setAccountsActionVisible(storageService, !isAccountsActionVisible(storageService)) }),
740
toAction({ id: 'toggle.hideManage', label: localize('manage', "Manage"), checked: true, enabled: false, run: () => { throw new Error('"Manage" can not be hidden'); } })
741
];
742
}
743
744
export function isAccountsActionVisible(storageService: IStorageService): boolean {
745
return storageService.getBoolean(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.PROFILE, true);
746
}
747
748
function setAccountsActionVisible(storageService: IStorageService, visible: boolean) {
749
storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, visible, StorageScope.PROFILE, StorageTarget.USER);
750
}
751
752