Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix account menu order, icon and badge #13771

Merged
merged 4 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions packages/core/src/browser/authentication-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export interface AuthenticationService {
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;

readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationProviderAuthenticationSessionsChangeEvent }>;
readonly onDidUpdateSignInCount: Event<number>;
getSessions(providerId: string, scopes?: string[]): Promise<ReadonlyArray<AuthenticationSession>>;
getLabel(providerId: string): string;
supportsMultipleAccounts(providerId: string): boolean;
Expand All @@ -157,15 +158,18 @@ export class AuthenticationServiceImpl implements AuthenticationService {

protected authenticationProviders: Map<string, AuthenticationProvider> = new Map<string, AuthenticationProvider>();

private onDidRegisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
private readonly onDidRegisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidRegisterAuthenticationProviderEmitter.event;

private onDidUnregisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
private readonly onDidUnregisterAuthenticationProviderEmitter: Emitter<AuthenticationProviderInformation> = new Emitter<AuthenticationProviderInformation>();
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this.onDidUnregisterAuthenticationProviderEmitter.event;

private onDidChangeSessionsEmitter: Emitter<SessionChangeEvent> = new Emitter<SessionChangeEvent>();
private readonly onDidChangeSessionsEmitter: Emitter<SessionChangeEvent> = new Emitter<SessionChangeEvent>();
readonly onDidChangeSessions: Event<SessionChangeEvent> = this.onDidChangeSessionsEmitter.event;

private readonly onDidChangeSignInCountEmitter: Emitter<number> = new Emitter<number>();
readonly onDidUpdateSignInCount: Event<number> = this.onDidChangeSignInCountEmitter.event;

@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
@inject(CommandRegistry) protected readonly commands: CommandRegistry;
@inject(StorageService) protected readonly storageService: StorageService;
Expand Down Expand Up @@ -295,6 +299,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
return;
}

const previousSize = this.signInRequestItems.size;
const sessions = await provider.getSessions();
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
Expand All @@ -311,6 +316,9 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}
}
});
if (previousSize !== this.signInRequestItems.size) {
this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
}
}

async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
Expand Down Expand Up @@ -341,7 +349,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}

const menuItem = this.menus.registerMenuAction(ACCOUNTS_SUBMENU, {
label: `Sign in to use ${extensionName} (1)`,
label: nls.localizeByDefault('Sign in with {0} to use {1} (1)', provider.label, extensionName),
order: '1',
commandId: `${extensionId}signIn`,
});
Expand All @@ -362,6 +370,7 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}
});

const previousSize = this.signInRequestItems.size;
if (providerRequests) {
const existingRequest = providerRequests[scopesList] || { disposables: [], requestingExtensionIds: [] };

Expand All @@ -378,6 +387,9 @@ export class AuthenticationServiceImpl implements AuthenticationService {
}
});
}
if (previousSize !== this.signInRequestItems.size) {
this.onDidChangeSignInCountEmitter.fire(this.signInRequestItems.size);
}
}
}

Expand Down
16 changes: 9 additions & 7 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ import { ConfirmDialog, confirmExit, ConfirmSaveDialog, Dialog } from './dialogs
import { WindowService } from './window/window-service';
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
import { DecorationStyle } from './decoration-style';
import { isPinned, Title, togglePinned, Widget } from './widgets';
import { codicon, isPinned, Title, togglePinned, Widget } from './widgets';
import { SaveableService } from './saveable-service';
import { UserWorkingDirectoryProvider } from './user-working-directory-provider';
import { UNTITLED_SCHEME, UntitledResourceResolver } from '../common';
import { LanguageQuickPickService } from './i18n/language-quick-pick-service';
import { SidebarMenu } from './shell/sidebar-menu-widget';

export namespace CommonMenus {

Expand Down Expand Up @@ -472,17 +473,18 @@ export class CommonFrontendContribution implements FrontendApplicationContributi

app.shell.leftPanelHandler.addBottomMenu({
id: 'settings-menu',
iconClass: 'codicon codicon-settings-gear',
iconClass: codicon('settings-gear'),
title: nls.localizeByDefault(CommonCommands.MANAGE_CATEGORY),
menuPath: MANAGE_MENU,
order: 1,
order: 0,
});
const accountsMenu = {
const accountsMenu: SidebarMenu = {
id: 'accounts-menu',
iconClass: 'codicon codicon-person',
iconClass: codicon('account'),
title: nls.localizeByDefault('Accounts'),
menuPath: ACCOUNTS_MENU,
order: 0,
order: 1,
onDidBadgeChange: this.authenticationService.onDidUpdateSignInCount
};
this.authenticationService.onDidRegisterAuthenticationProvider(() => {
app.shell.leftPanelHandler.addBottomMenu(accountsMenu);
Expand Down Expand Up @@ -530,7 +532,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
if (newValue === 'compact') {
this.shell.leftPanelHandler.addTopMenu({
id: mainMenuId,
iconClass: 'codicon codicon-menu',
iconClass: `theia-compact-menu ${codicon('menu')}`,
title: nls.localizeByDefault('Application Menu'),
menuPath: MAIN_MENU_BAR,
order: 0,
Expand Down
83 changes: 63 additions & 20 deletions packages/core/src/browser/shell/sidebar-menu-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ReactWidget } from '../widgets';
import { ContextMenuRenderer } from '../context-menu-renderer';
import { MenuPath } from '../../common/menu';
import { HoverService } from '../hover-service';
import { Event, Disposable, Emitter, DisposableCollection } from '../../common';

export const SidebarTopMenuWidgetFactory = Symbol('SidebarTopMenuWidgetFactory');
export const SidebarBottomMenuWidgetFactory = Symbol('SidebarBottomMenuWidgetFactory');
Expand All @@ -29,18 +30,54 @@ export interface SidebarMenu {
iconClass: string;
title: string;
menuPath: MenuPath;
onDidBadgeChange?: Event<number>;
/*
* Used to sort menus. The lower the value the lower they are placed in the sidebar.
*/
order: number;
}

export class SidebarMenuItem implements Disposable {

readonly menu: SidebarMenu;
get badge(): string {
if (this._badge <= 0) {
return '';
} else if (this._badge > 99) {
return '99+';
} else {
return this._badge.toString();
}
};
protected readonly onDidBadgeChangeEmitter = new Emitter<number>();
readonly onDidBadgeChange: Event<number> = this.onDidBadgeChangeEmitter.event;
protected _badge = 0;

protected readonly toDispose = new DisposableCollection();

constructor(menu: SidebarMenu) {
this.menu = menu;
if (menu.onDidBadgeChange) {
this.toDispose.push(menu.onDidBadgeChange(value => {
this._badge = value;
this.onDidBadgeChangeEmitter.fire(value);
}));
}
}

dispose(): void {
this.toDispose.dispose();
this.onDidBadgeChangeEmitter.dispose();
}

}

/**
* The menu widget placed on the sidebar.
*/
@injectable()
export class SidebarMenuWidget extends ReactWidget {
protected readonly menus: SidebarMenu[];
protected readonly items: SidebarMenuItem[];
/**
* The element that had focus when a menu rendered by this widget was activated.
*/
Expand All @@ -58,27 +95,27 @@ export class SidebarMenuWidget extends ReactWidget {

constructor() {
super();
this.menus = [];
this.items = [];
}

addMenu(menu: SidebarMenu): void {
const exists = this.menus.find(m => m.id === menu.id);
const exists = this.items.find(item => item.menu.id === menu.id);
if (exists) {
return;
}
this.menus.push(menu);
this.menus.sort((a, b) => a.order - b.order);
const newItem = new SidebarMenuItem(menu);
newItem.onDidBadgeChange(() => this.update());
this.items.push(newItem);
this.items.sort((a, b) => a.menu.order - b.menu.order);
this.update();
}

removeMenu(menuId: string): void {
const menu = this.menus.find(m => m.id === menuId);
if (menu) {
const index = this.menus.indexOf(menu);
if (index !== -1) {
this.menus.splice(index, 1);
this.update();
}
const index = this.items.findIndex(m => m.menu.id === menuId);
if (index !== -1) {
this.items[index].dispose();
this.items.splice(index, 1);
this.update();
}
}

Expand Down Expand Up @@ -127,14 +164,20 @@ export class SidebarMenuWidget extends ReactWidget {

protected render(): React.ReactNode {
return <React.Fragment>
{this.menus.map(menu => <i
key={menu.id}
className={menu.iconClass}
onClick={e => this.onClick(e, menu.menuPath)}
onMouseDown={this.onMouseDown}
onMouseEnter={e => this.onMouseEnter(e, menu.title)}
onMouseLeave={this.onMouseOut}
/>)}
{this.items.map(item => this.renderItem(item))}
</React.Fragment>;
}

protected renderItem(item: SidebarMenuItem): React.ReactNode {
return <div
key={item.menu.id}
className='theia-sidebar-menu-item'
onClick={e => this.onClick(e, item.menu.menuPath)}
onMouseDown={this.onMouseDown}
onMouseEnter={e => this.onMouseEnter(e, item.menu.title)}
onMouseLeave={this.onMouseOut}>
<i className={item.menu.iconClass} />
{item.badge && <div className='theia-badge-decorator-sidebar'>{item.badge}</div>}
</div>;
}
}
9 changes: 6 additions & 3 deletions packages/core/src/browser/style/sidepanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,23 +186,26 @@
flex-direction: column-reverse;
}

.p-Widget .theia-sidebar-menu-item {
cursor: pointer;
}

.p-Widget.theia-sidebar-menu i {
padding: var(--theia-private-sidebar-tab-padding-top-and-bottom)
var(--theia-private-sidebar-tab-padding-left-and-right);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--theia-activityBar-inactiveForeground);
background-color: var(--theia-activityBar-background);
font-size: var(--theia-private-sidebar-icon-size);
}

.theia-sidebar-menu i:hover {
.theia-sidebar-menu .theia-sidebar-menu-item:hover i {
color: var(--theia-activityBar-foreground);
}

.theia-sidebar-menu > i.codicon-menu {
.theia-sidebar-menu i.theia-compact-menu {
font-size: 16px;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@
display: none !important;
}

.p-TabBar .theia-badge-decorator-sidebar {
.theia-badge-decorator-sidebar {
background-color: var(--theia-activityBarBadge-background);
border-radius: 20px;
color: var(--theia-activityBarBadge-foreground);
Expand Down
Loading