Skip to content

Commit

Permalink
core: add support for badge decorations
Browse files Browse the repository at this point in the history
The following commit adds support for a new type of widget decoration called a
`badge` which is essentially a notification for which widgets can contribute
a counter to for display purposes.

The commit includes two new client contributions from:
- `scm`: contributes a `badge` counter for number of changes in the working tree.
- `markers`: contributes a `badge` counter for the number of problem marker stats.

Co-authored-by: Kaiyue Pan <kaiyue.pan@ericsson.com>
Co-authored-by: vince-fugnitto <vincent.fugnitto@ericsson.com>

Signed-off-by: Kaiyue Pan <kaiyue.pan@ericsson.com>
Signed-off-by: vince-fugnitto <vincent.fugnitto@ericsson.com>
  • Loading branch information
Kaiyue Pan authored and vince-fugnitto committed Aug 3, 2020
1 parent 8ac1e5f commit 747404b
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 14 deletions.
15 changes: 3 additions & 12 deletions packages/core/src/browser/shell/tab-bar-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import debounce = require('lodash.debounce');
import { Title, Widget } from '@phosphor/widgets';
import { inject, injectable, named, postConstruct } from 'inversify';
import { Event, Emitter, Disposable, DisposableCollection, ContributionProvider } from '../../common';
import { Event, Emitter, ContributionProvider } from '../../common';
import { WidgetDecoration } from '../widget-decoration';

export const TabBarDecorator = Symbol('TabBarDecorator');
Expand All @@ -43,27 +43,18 @@ export interface TabBarDecorator {
}

@injectable()
export class TabBarDecoratorService implements Disposable {
export class TabBarDecoratorService {

protected readonly onDidChangeDecorationsEmitter = new Emitter<void>();

readonly onDidChangeDecorations = this.onDidChangeDecorationsEmitter.event;

protected readonly toDispose = new DisposableCollection();

@inject(ContributionProvider) @named(TabBarDecorator)
protected readonly contributions: ContributionProvider<TabBarDecorator>;

@postConstruct()
protected init(): void {
const decorators = this.contributions.getContributions();
this.toDispose.pushAll(decorators.map(decorator =>
decorator.onDidChangeDecorations(this.fireDidChangeDecorations)
));
}

dispose(): void {
this.toDispose.dispose();
this.contributions.getContributions().map(decorator => decorator.onDidChangeDecorations(this.fireDidChangeDecorations));
}

protected fireDidChangeDecorations = debounce(() => this.onDidChangeDecorationsEmitter.fire(undefined), 150);
Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export class TabBarRenderer extends TabBar.Renderer {
super();
if (this.decoratorService) {
this.toDispose.push(Disposable.create(() => this.resetDecorations()));
this.toDispose.push(this.decoratorService);
this.toDispose.push(this.decoratorService.onDidChangeDecorations(() => this.resetDecorations()));
}
if (this.iconThemeService) {
Expand Down Expand Up @@ -151,7 +150,8 @@ export class TabBarRenderer extends TabBar.Renderer {
h.div(
{ className: 'theia-tab-icon-label' },
this.renderIcon(data, isInSidePanel),
this.renderLabel(data, isInSidePanel)
this.renderLabel(data, isInSidePanel),
this.renderBadge(data, isInSidePanel)
),
this.renderCloseIcon(data)
);
Expand Down Expand Up @@ -226,6 +226,17 @@ export class TabBarRenderer extends TabBar.Renderer {
return h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label);
}

renderBadge(data: SideBarRenderData, isInSidePanel?: boolean): VirtualElement {
const badge: number | undefined = this.getDecorationData(data.title, 'badge')[0];
if (!badge) {
return h.div({});
}
const limitedBadge = badge >= 100 ? '99+' : badge;
return isInSidePanel
? h.div({ className: 'theia-badge-decorator-sidebar' }, `${limitedBadge}`)
: h.div({ className: 'theia-badge-decorator-horizontal' }, `${limitedBadge}`);
}

protected readonly decorations = new Map<Title<Widget>, WidgetDecoration.Data[]>();

protected resetDecorations(title?: Title<Widget>): void {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/browser/style/notification.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

:root {
--theia-notification-count-height: 15.5px;
--theia-notification-count-width: 15.5px;
}

.notification-count-container {
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,37 @@ body.theia-editor-highlightModifiedTabs
display: none !important;
}

.p-TabBar .theia-badge-decorator-sidebar {
background-color: var(--theia-activityBarBadge-background);
border-radius: 20px;
color: var(--theia-activityBarBadge-foreground);
font-size: calc(var(--theia-ui-font-size0) * 0.85);
font-weight: 600;
height: var(--theia-notification-count-height);
width: var(--theia-notification-count-width);
padding: calc(var(--theia-ui-padding)/6);
line-height: calc(var(--theia-content-line-height) * 0.70);
position: absolute;
top: calc(var(--theia-ui-padding) * 4);
right: calc(var(--theia-ui-padding) * 1.33);
text-align: center;
}

.p-TabBar .theia-badge-decorator-horizontal {
background-color:var(--theia-badge-background);
border-radius: 20px;
box-sizing: border-box;
color: var(--theia-activityBarBadge-foreground);
font-size: calc(var(--theia-ui-font-size0) * 0.8);
font-weight: 400;
height: var(--theia-notification-count-height);
width: var(--theia-notification-count-width);
padding: calc(var(--theia-ui-padding)/6);
line-height: calc(var(--theia-content-line-height) * 0.61);
margin-left: var(--theia-ui-padding);
text-align: center;
}

/*-----------------------------------------------------------------------------
| Perfect scrollbar
|----------------------------------------------------------------------------*/
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/widget-decoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ export namespace WidgetDecoration {
* An array of ranges to highlight the caption.
*/
readonly highlight?: CaptionHighlight;
/**
* A count badge for widgets.
*/
readonly badge?: number;
}
export namespace Data {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ProblemLayoutVersion3Migration } from './problem-layout-migrations';
import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { bindProblemPreferences } from './problem-preferences';
import { MarkerTreeLabelProvider } from '../marker-tree-label-provider';
import { ProblemWidgetTabBarDecorator } from './problem-widget-tab-bar-decorator';

export default new ContainerModule(bind => {
bindProblemPreferences(bind);
Expand All @@ -57,4 +58,7 @@ export default new ContainerModule(bind => {

bind(MarkerTreeLabelProvider).toSelf().inSingletonScope();
bind(LabelProviderContribution).toService(MarkerTreeLabelProvider);

bind(ProblemWidgetTabBarDecorator).toSelf().inSingletonScope();
bind(TabBarDecorator).toService(ProblemWidgetTabBarDecorator);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/********************************************************************************
* Copyright (C) 2020 Ericsson and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject, postConstruct } from 'inversify';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { ProblemManager } from './problem-manager';
import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { Title, Widget } from '@theia/core/lib/browser';
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';

@injectable()
export class ProblemWidgetTabBarDecorator implements TabBarDecorator {

readonly id = 'theia-problems-widget-tabbar-decorator';
protected readonly emitter = new Emitter<void>();

@inject(ProblemManager)
protected readonly problemManager: ProblemManager;

@postConstruct()
protected init(): void {
this.problemManager.onDidChangeMarkers(() => this.fireDidChangeDecorations());
}

decorate(title: Title<Widget>): WidgetDecoration.Data[] {
if (title.owner.id === 'problems') {
const { infos, warnings, errors } = this.problemManager.getProblemStat();
const markerCount = infos + warnings + errors;
return markerCount > 0 ? [{ badge: markerCount }] : [];
} else {
return [];
}
}

get onDidChangeDecorations(): Event<void> {
return this.emitter.event;
}

protected fireDidChangeDecorations(): void {
this.emitter.fire(undefined);
}
}
82 changes: 82 additions & 0 deletions packages/scm/src/browser/decorations/scm-tab-bar-decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/********************************************************************************
* Copyright (C) 2020 Ericsson and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject, postConstruct } from 'inversify';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { SCM_VIEW_CONTAINER_ID } from '../scm-contribution';
import { ScmService } from '../scm-service';
import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator';
import { Title, Widget } from '@theia/core/lib/browser';
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
import { DisposableCollection } from '@theia/core/lib/common/disposable';

@injectable()
export class ScmTabBarDecorator implements TabBarDecorator {

readonly id = 'theia-scm-tabbar-decorator';
protected readonly emitter = new Emitter<void>();

private readonly toDispose = new DisposableCollection();
private readonly toDisposeOnDidChange = new DisposableCollection();

@inject(ScmService)
protected readonly scmService: ScmService;

@postConstruct()
protected init(): void {
this.toDispose.push(this.scmService.onDidChangeSelectedRepository(repository => {
this.toDisposeOnDidChange.dispose();
if (repository) {
this.toDisposeOnDidChange.push(
repository.provider.onDidChange(() => this.fireDidChangeDecorations())
);
}
this.fireDidChangeDecorations();
}));
}

decorate(title: Title<Widget>): WidgetDecoration.Data[] {
if (title.owner.id === SCM_VIEW_CONTAINER_ID) {
const changes = this.collectChangesCount();
return changes > 0 ? [{ badge: changes }] : [];
} else {
return [];
}
}

protected collectChangesCount(): number {
const repository = this.scmService.selectedRepository;
let changes = 0;
if (!repository) {
return 0;
}
repository.provider.groups.map(group => {
if (group.id === 'index' || group.id === 'workingTree') {
changes += group.resources.length;
}
});
return changes;
}

get onDidChangeDecorations(): Event<void> {
return this.emitter.event;
}

protected fireDidChangeDecorations(): void {
this.emitter.fire(undefined);
}

}
5 changes: 5 additions & 0 deletions packages/scm/src/browser/scm-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar
import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
import { LabelProviderContribution } from '@theia/core/lib/browser/label-provider';
import { bindScmPreferences } from './scm-preferences';
import { ScmTabBarDecorator } from './decorations/scm-tab-bar-decorator';
import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator';

export default new ContainerModule(bind => {
bind(ScmContextKeyService).toSelf().inSingletonScope();
Expand Down Expand Up @@ -118,6 +120,9 @@ export default new ContainerModule(bind => {
bind(LabelProviderContribution).toService(ScmTreeLabelProvider);

bindScmPreferences(bind);

bind(ScmTabBarDecorator).toSelf().inSingletonScope();
bind(TabBarDecorator).toService(ScmTabBarDecorator);
});

export function createScmTreeContainer(parent: interfaces.Container): Container {
Expand Down

0 comments on commit 747404b

Please sign in to comment.