Skip to content

Commit 3718e63

Browse files
author
Rachel Macfarlane
committed
Use submenu instead of quickpick for showing account commands, fixes microsoft#99901
1 parent fe70e8e commit 3718e63

3 files changed

Lines changed: 91 additions & 80 deletions

File tree

src/vs/workbench/api/browser/mainThreadAuthentication.ts

Lines changed: 18 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
6+
import { Disposable } from 'vs/base/common/lifecycle';
77
import * as modes from 'vs/editor/common/modes';
88
import * as nls from 'vs/nls';
99
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
@@ -12,8 +12,6 @@ import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContex
1212
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
1313
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
1414
import Severity from 'vs/base/common/severity';
15-
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
16-
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
1715
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
1816
import { INotificationService } from 'vs/platform/notification/common/notification';
1917
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
@@ -71,7 +69,6 @@ function addAccountUsage(storageService: IStorageService, providerId: string, ac
7169
}
7270

7371
export class MainThreadAuthenticationProvider extends Disposable {
74-
private _sessionMenuItems = new Map<string, IDisposable[]>();
7572
private _accounts = new Map<string, string[]>(); // Map account name to session ids
7673
private _sessions = new Map<string, string>(); // Map account id to name
7774

@@ -82,7 +79,9 @@ export class MainThreadAuthenticationProvider extends Disposable {
8279
public readonly supportsMultipleAccounts: boolean,
8380
private readonly notificationService: INotificationService,
8481
private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
85-
private readonly storageService: IStorageService
82+
private readonly storageService: IStorageService,
83+
private readonly quickInputService: IQuickInputService,
84+
private readonly dialogService: IDialogService
8685
) {
8786
super();
8887
}
@@ -95,11 +94,11 @@ export class MainThreadAuthenticationProvider extends Disposable {
9594
return !!this._sessions.size;
9695
}
9796

98-
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
99-
const quickPick = quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>();
97+
public manageTrustedExtensions(accountName: string) {
98+
const quickPick = this.quickInputService.createQuickPick<{ label: string, description: string, extension: AllowedExtension }>();
10099
quickPick.canSelectMany = true;
101-
const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName);
102-
const usages = readAccountUsages(storageService, this.id, accountName);
100+
const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName);
101+
const usages = readAccountUsages(this.storageService, this.id, accountName);
103102
const items = allowedExtensions.map(extension => {
104103
const usage = usages.find(usage => extension.id === usage.extensionId);
105104
return {
@@ -118,7 +117,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
118117

119118
quickPick.onDidAccept(() => {
120119
const updatedAllowedList = quickPick.selectedItems.map(item => item.extension);
121-
storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL);
120+
this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL);
122121

123122
quickPick.dispose();
124123
});
@@ -146,69 +145,23 @@ export class MainThreadAuthenticationProvider extends Disposable {
146145
this._accounts.set(session.account.displayName, [session.id]);
147146
}
148147

149-
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
150-
group: '1_accounts',
151-
command: {
152-
id: `configureSessions${session.id}`,
153-
title: `${session.account.displayName} (${this.displayName})`
154-
},
155-
order: 3
156-
});
157-
158148
this.storageKeysSyncRegistryService.registerStorageKey({ key: `${this.id}-${session.account.displayName}`, version: 1 });
159-
160-
const manageCommand = CommandsRegistry.registerCommand({
161-
id: `configureSessions${session.id}`,
162-
handler: (accessor, args) => {
163-
const quickInputService = accessor.get(IQuickInputService);
164-
const storageService = accessor.get(IStorageService);
165-
const dialogService = accessor.get(IDialogService);
166-
167-
const quickPick = quickInputService.createQuickPick();
168-
const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
169-
const signOut = nls.localize('signOut', "Sign Out");
170-
const items = ([{ label: manage }, { label: signOut }]);
171-
172-
quickPick.items = items;
173-
174-
quickPick.onDidAccept(e => {
175-
const selected = quickPick.selectedItems[0];
176-
if (selected.label === signOut) {
177-
this.signOut(dialogService, session);
178-
}
179-
180-
if (selected.label === manage) {
181-
this.manageTrustedExtensions(quickInputService, storageService, session.account.displayName);
182-
}
183-
184-
quickPick.dispose();
185-
});
186-
187-
quickPick.onDidHide(_ => {
188-
quickPick.dispose();
189-
});
190-
191-
quickPick.show();
192-
},
193-
});
194-
195-
this._sessionMenuItems.set(session.account.displayName, [menuItem, manageCommand]);
196149
}
197150

198-
async signOut(dialogService: IDialogService, session: modes.AuthenticationSession): Promise<void> {
199-
const accountUsages = readAccountUsages(this.storageService, this.id, session.account.displayName);
200-
const sessionsForAccount = this._accounts.get(session.account.displayName);
151+
async signOut(accountName: string): Promise<void> {
152+
const accountUsages = readAccountUsages(this.storageService, this.id, accountName);
153+
const sessionsForAccount = this._accounts.get(accountName);
201154

202-
const result = await dialogService.confirm({
203-
title: nls.localize('signOutConfirm', "Sign out of {0}", session.account.displayName),
155+
const result = await this.dialogService.confirm({
156+
title: nls.localize('signOutConfirm', "Sign out of {0}", accountName),
204157
message: accountUsages.length
205-
? nls.localize('signOutMessagve', "The account {0} has been used by: \n\n{1}\n\n Sign out of these features?", session.account.displayName, accountUsages.map(usage => usage.extensionName).join('\n'))
206-
: nls.localize('signOutMessageSimple', "Sign out of {0}?", session.account.displayName)
158+
? nls.localize('signOutMessagve', "The account {0} has been used by: \n\n{1}\n\n Sign out of these features?", accountName, accountUsages.map(usage => usage.extensionName).join('\n'))
159+
: nls.localize('signOutMessageSimple', "Sign out of {0}?", accountName)
207160
});
208161

209162
if (result.confirmed) {
210163
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
211-
removeAccountUsage(this.storageService, this.id, session.account.displayName);
164+
removeAccountUsage(this.storageService, this.id, accountName);
212165
}
213166
}
214167

@@ -230,11 +183,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
230183
sessionsForAccount.splice(sessionIndex);
231184

232185
if (!sessionsForAccount.length) {
233-
const disposeables = this._sessionMenuItems.get(accountName);
234-
if (disposeables) {
235-
disposeables.forEach(disposeable => disposeable.dispose());
236-
this._sessionMenuItems.delete(accountName);
237-
}
238186
this._accounts.delete(accountName);
239187
}
240188
}
@@ -251,12 +199,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
251199
await this._proxy.$logout(this.id, sessionId);
252200
this.notificationService.info(nls.localize('signedOut', "Successfully signed out."));
253201
}
254-
255-
dispose(): void {
256-
super.dispose();
257-
this._sessionMenuItems.forEach(item => item.forEach(d => d.dispose()));
258-
this._sessionMenuItems.clear();
259-
}
260202
}
261203

262204
@extHostNamedCustomer(MainContext.MainThreadAuthentication)
@@ -294,7 +236,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
294236
}
295237

296238
async $registerAuthenticationProvider(id: string, displayName: string, supportsMultipleAccounts: boolean): Promise<void> {
297-
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService);
239+
const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, supportsMultipleAccounts, this.notificationService, this.storageKeysSyncRegistryService, this.storageService, this.quickInputService, this.dialogService);
298240
await provider.initialize();
299241
this.authenticationService.registerAuthenticationProvider(id, provider);
300242
}

src/vs/workbench/browser/parts/activitybar/activitybarActions.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch
1111
import { Action, IAction } from 'vs/base/common/actions';
1212
import { KeyCode } from 'vs/base/common/keyCodes';
1313
import { dispose } from 'vs/base/common/lifecycle';
14-
import { SyncActionDescriptor, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
14+
import { SyncActionDescriptor, IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
1515
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
1616
import { Registry } from 'vs/platform/registry/common/platform';
1717
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -28,8 +28,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2828
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
2929
import { ICommandService } from 'vs/platform/commands/common/commands';
3030
import { Codicon } from 'vs/base/common/codicons';
31-
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
31+
import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
3232
import { isMacintosh } from 'vs/base/common/platform';
33+
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
34+
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
35+
import { distinct } from 'vs/base/common/arrays';
3336

3437
export class ViewContainerActivityAction extends ActivityAction {
3538

@@ -103,6 +106,7 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
103106
@IContextMenuService protected contextMenuService: IContextMenuService,
104107
@IMenuService protected menuService: IMenuService,
105108
@IContextKeyService private readonly contextKeyService: IContextKeyService,
109+
@IAuthenticationService private readonly authenticationService: IAuthenticationService
106110
) {
107111
super(action, { draggable: false, colors, icon: true }, themeService);
108112
}
@@ -132,16 +136,60 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
132136
}));
133137
}
134138

135-
private showContextMenu(): void {
139+
private async getActions(accountsMenu: IMenu) {
140+
const otherCommands = accountsMenu.getActions();
141+
const providers = this.authenticationService.getProviderIds();
142+
const allSessions = providers.map(async id => {
143+
const sessions = await this.authenticationService.getSessions(id);
144+
const uniqueSessions = distinct(sessions, session => session.account.displayName);
145+
return {
146+
providerId: id,
147+
sessions: uniqueSessions
148+
};
149+
});
150+
151+
const result = await Promise.all(allSessions);
152+
let menus: (IAction | ContextSubMenu)[] = [];
153+
result.forEach(sessionInfo => {
154+
const providerDisplayName = this.authenticationService.getDisplayName(sessionInfo.providerId);
155+
sessionInfo.sessions.forEach(session => {
156+
const accountName = session.account.displayName;
157+
const menu = new ContextSubMenu(`${accountName} (${providerDisplayName})`, [
158+
new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, _ => {
159+
return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName);
160+
}),
161+
new Action('signOut', nls.localize('signOut', "Sign Out"), '', true, _ => {
162+
return this.authenticationService.signOutOfAccount(sessionInfo.providerId, accountName);
163+
})
164+
]);
165+
menus.push(menu);
166+
});
167+
});
168+
169+
if (menus.length) {
170+
menus.push(new Separator());
171+
}
172+
173+
otherCommands.forEach(group => {
174+
const actions = group[1];
175+
menus = menus.concat(actions);
176+
menus.push(new Separator());
177+
});
178+
179+
return menus;
180+
}
181+
182+
private async showContextMenu(): Promise<void> {
136183
const accountsActions: IAction[] = [];
137184
const accountsMenu = this.menuService.createMenu(MenuId.AccountsContext, this.contextKeyService);
138185
const actionsDisposable = createAndFillInActionBarActions(accountsMenu, undefined, { primary: [], secondary: accountsActions });
139186

140187
const containerPosition = DOM.getDomNodePagePosition(this.container);
141188
const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top };
189+
const actions = await this.getActions(accountsMenu);
142190
this.contextMenuService.showContextMenu({
143191
getAnchor: () => location,
144-
getActions: () => accountsActions,
192+
getActions: () => actions,
145193
onHide: () => {
146194
accountsMenu.dispose();
147195
dispose(actionsDisposable);

src/vs/workbench/services/authentication/browser/authenticationService.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export interface IAuthenticationService {
3737
supportsMultipleAccounts(providerId: string): boolean;
3838
login(providerId: string, scopes: string[]): Promise<AuthenticationSession>;
3939
logout(providerId: string, sessionId: string): Promise<void>;
40+
41+
manageTrustedExtensionsForAccount(providerId: string, accountName: string): Promise<void>;
42+
signOutOfAccount(providerId: string, accountName: string): Promise<void>;
4043
}
4144

4245
export interface AllowedExtension {
@@ -330,6 +333,24 @@ export class AuthenticationService extends Disposable implements IAuthentication
330333
throw new Error(`No authentication provider '${id}' is currently registered.`);
331334
}
332335
}
336+
337+
async manageTrustedExtensionsForAccount(id: string, accountName: string): Promise<void> {
338+
const authProvider = this._authenticationProviders.get(id);
339+
if (authProvider) {
340+
return authProvider.manageTrustedExtensions(accountName);
341+
} else {
342+
throw new Error(`No authentication provider '${id}' is currently registered.`);
343+
}
344+
}
345+
346+
async signOutOfAccount(id: string, accountName: string): Promise<void> {
347+
const authProvider = this._authenticationProviders.get(id);
348+
if (authProvider) {
349+
return authProvider.signOut(accountName);
350+
} else {
351+
throw new Error(`No authentication provider '${id}' is currently registered.`);
352+
}
353+
}
333354
}
334355

335356
registerSingleton(IAuthenticationService, AuthenticationService);

0 commit comments

Comments
 (0)