Skip to content

Commit

Permalink
Notification Center UI
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Tugarev <alex.tugarev@typefox.io>
  • Loading branch information
AlexTugarev committed Jul 31, 2019
1 parent 98bf44f commit 0d2db8b
Show file tree
Hide file tree
Showing 15 changed files with 872 additions and 342 deletions.
2 changes: 2 additions & 0 deletions packages/core/src/browser/style/variables-bright.useable.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ is not optimized for dense, information rich UIs.
--theia-icon-regex: url(../icons/regex.svg);
--theia-icon-whole-word: url(../icons/whole-word.svg);
--theia-icon-refresh: url(../icons/Refresh.svg);
--theia-icon-expand: url(../icons/expand.svg);
--theia-icon-collapse: url(../icons/collapse.svg);
--theia-icon-collapse-all: url(../icons/CollapseAll.svg);
--theia-icon-clear: url(../icons/clear-search-results.svg);
--theia-icon-replace: url(../icons/replace.svg);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/browser/style/variables-dark.useable.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ is not optimized for dense, information rich UIs.
--theia-icon-regex: url(../icons/regex-dark.svg);
--theia-icon-whole-word: url(../icons/whole-word-dark.svg);
--theia-icon-refresh: url(../icons/Refresh_inverse.svg);
--theia-icon-expand: url(../icons/expand.svg);
--theia-icon-collapse: url(../icons/collapse.svg);
--theia-icon-collapse-all: url(../icons/CollapseAll_inverse.svg);
--theia-icon-clear: url(../icons/clear-search-results-dark.svg);
--theia-icon-replace: url(../icons/replace-inverse.svg);
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/common/message-service-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface Message {
readonly text: string;
readonly actions?: string[];
readonly options?: MessageOptions;
readonly source?: string;
}

export interface ProgressMessage extends Message {
Expand All @@ -48,6 +49,10 @@ export namespace ProgressMessage {
}

export interface MessageOptions {
/**
* Timeout in milliseconds.
* `0` and negative values are treated as no timeout.
*/
readonly timeout?: number;
}

Expand Down Expand Up @@ -86,7 +91,7 @@ export class MessageClient {
}

/**
* Show progress message with possible actions to user.
* Show a progress message with possible actions to user.
*
* To be implemented by an extension, e.g. by the messages extension.
*/
Expand All @@ -96,7 +101,7 @@ export class MessageClient {
}

/**
* Update started progress message.
* Update a previously created progress message.
*
* To be implemented by an extension, e.g. by the messages extension.
*/
Expand Down
6 changes: 5 additions & 1 deletion packages/messages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"version": "0.9.0",
"description": "Theia - Messages Extension",
"dependencies": {
"@theia/core": "^0.9.0"
"@theia/core": "^0.9.0",
"lodash.throttle": "*",
"markdown-it": "^8.4.0",
"react-perfect-scrollbar": "^1.5.3",
"ts-md5": "*"
},
"publishConfig": {
"access": "public"
Expand Down
23 changes: 17 additions & 6 deletions packages/messages/src/browser/messages-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,27 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import '../../src/browser/style/index.css';

import { ContainerModule } from 'inversify';
import { MessageClient } from '@theia/core/lib/common';

import { NotificationsMessageClient } from './notifications-message-client';

import '../../src/browser/style/index.css';
import { NotificationManager, NotificationManagerImpl } from './notifications-manager';
import { bindNotificationPreferences } from './notification-preferences';
import { NotificationCenter } from './notification-center';
import { NotificationsContribution, NotificationsKeybindingContext } from './notifications-contribution';
import { FrontendApplicationContribution, KeybindingContribution, KeybindingContext } from '@theia/core/lib/browser';
import { CommandContribution } from '@theia/core';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotificationCenter).toSelf().inSingletonScope();
bind(NotificationsContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(NotificationsContribution);
bind(CommandContribution).toService(NotificationsContribution);
bind(KeybindingContribution).toService(NotificationsContribution);
bind(NotificationsKeybindingContext).toSelf().inSingletonScope();
bind(KeybindingContext).toService(NotificationsKeybindingContext);
bind(NotificationManagerImpl).toSelf().inSingletonScope();
bind(NotificationManager).toService(NotificationManagerImpl);
rebind(MessageClient).toService(NotificationManagerImpl);
bindNotificationPreferences(bind);
bind(NotificationsMessageClient).toSelf().inSingletonScope();
rebind(MessageClient).toService(NotificationsMessageClient);
});
192 changes: 192 additions & 0 deletions packages/messages/src/browser/notification-center.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/********************************************************************************
* Copyright (C) 2019 TypeFox 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 * as ReactDOM from 'react-dom';
import * as React from 'react';
import { injectable, inject, postConstruct } from 'inversify';
import { ApplicationShell } from '@theia/core/lib/browser';
import { DisposableCollection } from '@theia/core';
import { NotificationManager, Notification } from './notifications-manager';

const PerfectScrollbar = require('react-perfect-scrollbar');

@injectable()
export class NotificationCenter {

@inject(ApplicationShell)
protected readonly shell: ApplicationShell;

@inject(NotificationManager)
protected readonly manager: NotificationManager;

@postConstruct()
protected async init() {
this.createOverlayContainer();
this.render();
}

protected container: HTMLDivElement;
protected createOverlayContainer() {
this.container = window.document.createElement('div');
this.container.className = 'theia-notification-center-overlay';
if (window.document.body) {
window.document.body.appendChild(this.container);
}
}

get open(): boolean {
return this.manager.open;
}

toggle(): void {
this.manager.toggle();
}

protected onHide(): void {
this.manager.hide();
}

clearAll(): void {
this.manager.clearAll();
}

protected render() {
ReactDOM.render(<NotificationCenterComponent manager={this.manager} />, this.container);
}

}

export interface NotificationCenterComponentProps {
readonly manager: NotificationManager;
}

interface NotificationCenterComponentState extends NotificationManager.UpdateEvent { }

export class NotificationCenterComponent extends React.Component<NotificationCenterComponentProps, NotificationCenterComponentState> {

constructor(props: NotificationCenterComponentProps) {
super(props);
this.state = {
notifications: [],
open: false
};
}

protected readonly toDisposeOnUnmount = new DisposableCollection();

async componentDidMount() {
this.toDisposeOnUnmount.push(
this.props.manager.onUpdate(event => {
this.setState(event);
})
);
}
componentWillUnmount() {
this.toDisposeOnUnmount.dispose();
}

render() {
const empty = this.state.notifications.length === 0;
const showHeader = empty ? { display: 'flex' } : {};
const title = empty ? 'NO NOTIFICATIONS' : 'NOTIFICATIONS';
return (
<div className={`theia-notification-center ${this.state.open ? 'open' : 'closed'}`}>
<div className='theia-notification-center-header' style={showHeader}>
<div className='theia-notification-center-header-title'>{title}</div>
<div className='theia-notification-center-header-actions'>
<ul className='theia-notification-actions'>
<li className='collapse' title='Hide' onClick={this.onHide.bind(this)} />
<li className='clear' title='Clear All' onClick={this.onClearAll.bind(this)} />
</ul>
</div>
</div>
<PerfectScrollbar className='theia-notification-list-scroll-container'>
<div className='theia-notification-list'>
{this.state.notifications.map(notification => this.renderNotification(notification))}
</div>
</PerfectScrollbar>
</div>
);
}

protected onHide() {
this.props.manager.hide();
}

protected onClearAll() {
this.props.manager.clearAll();
}

protected onClear(messageId: string) {
this.props.manager.clear(messageId);
}

protected onToggleExpansion(messageId: string) {
this.props.manager.toggleExpansion(messageId);
}

protected onAction(messageId: string, action: string) {
this.props.manager.accept(messageId, action);
}

protected messageClickeHandler(event: React.MouseEvent) {
if (event.target instanceof HTMLAnchorElement) {
event.stopPropagation();
event.preventDefault();
const link = event.target.href;
this.props.manager.openLink(link);
}
}

protected renderNotification(notification: Notification) {
const { messageId, message, type, progress, collapsed, expandable } = notification;
return (<div key={messageId} className='theia-notification-list-item'>
<div className={`theia-notification-list-item-content ${collapsed ? 'collapsed' : ''}`}>
<div className='theia-notification-list-item-content-main'>
<div className={`theia-notification-icon theia-notification-icon-${type}`} />
<div className='theia-notification-message'>
<span dangerouslySetInnerHTML={{ __html: message }} onClick={this.messageClickeHandler.bind(this)} />
</div>
<ul className='theia-notification-actions'>
{expandable && (
<li className={collapsed ? 'expand' : 'collapse'} title={collapsed ? 'Expand' : 'Collapse'} onClick={() => this.onToggleExpansion(messageId)} />
)}
<li className='clear' title='Clear' onClick={() => this.onClear(messageId)} />
</ul>
</div>
<div className='theia-notification-list-item-content-bottom'>
<div className='theia-notification-source'>
{notification.source && (<span>{notification.source}</span>)}
</div>
<div className='theia-notification-buttons'>
{notification.actions && notification.actions.map((action, index) => (
<button key={messageId + `-action-${index}`} className='theia-button'
onClick={() => this.onAction(messageId, action)}>
{action}
</button>
))}
</div>
</div>
</div>
{typeof progress === 'number' && (
<div className='theia-notification-item-progress'>
<div className='theia-notification-item-progressbar' style={{ width: `${progress}px` }} />
</div>
)}
</div>);
}

}
4 changes: 2 additions & 2 deletions packages/messages/src/browser/notification-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const NotificationConfigSchema: PreferenceSchema = {
'properties': {
'notification.timeout': {
'type': 'number',
'description': 'The time before auto-dismiss the notification.',
'default': 5000 // time express in millisec. 0 means : Do not remove
'description': 'The timeout for informative notifications.',
'default': 30 * 1000 // `0` and negative values are treated as no timeout.
}
}
};
Expand Down
36 changes: 36 additions & 0 deletions packages/messages/src/browser/notifications-commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/********************************************************************************
* Copyright (C) 2019 TypeFox 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 { Command } from '@theia/core';

export namespace NotificationsCommands {

const NOTIFICATIONS_CATEGORY = 'Notifications';

export const TOGGLE: Command = {
id: 'notifications.commands.toggle',
category: NOTIFICATIONS_CATEGORY,
iconClass: 'fa fa-th-list',
label: 'Toggle Notifications'
};

export const CLEAR_ALL: Command = {
id: 'notifications.commands.clearAll',
category: NOTIFICATIONS_CATEGORY,
iconClass: 'fa fa-times-circle',
label: 'Clear All Notifications'
};
}
Loading

0 comments on commit 0d2db8b

Please sign in to comment.