Skip to content

Commit

Permalink
fixes #2802 Introduce 'window.withProgress' API endpoint
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Vinokur <ivinokur@redhat.com>
  • Loading branch information
vinokurig committed Sep 28, 2018
1 parent 09095b0 commit ea443a6
Show file tree
Hide file tree
Showing 14 changed files with 651 additions and 25 deletions.
28 changes: 26 additions & 2 deletions packages/core/src/common/message-service-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

import { injectable, inject } from 'inversify';
import { ILogger } from './logger';
import { Event } from '../common/event';

export const messageServicePath = '/services/messageService';

export enum MessageType {
Error = 1,
Warning = 2,
Info = 3,
Log = 4
Log = 4,
Progress = 5
}

export interface Message {
Expand All @@ -33,6 +35,18 @@ export interface Message {
options?: MessageOptions;
}

export interface ProgressMessage {
show(): void;
close(): void;
update(item: { message?: string, increment?: number }): void;
onCancel: Event<void>;
}

export interface ProgressMessageArguments {
text: string;
actions?: string[];
}

export interface MessageOptions {
timeout?: number;
}
Expand All @@ -53,7 +67,17 @@ export class MessageClient {
this.logger.info(message.text);
return Promise.resolve(undefined);
}
}

/**
* Create progress message instance.
* If a progress message with given text is already shown, returns the shown instance.
*
* To be implemented by an extension, e.g. by the messages extension.
*/
getOrCreateProgressMessage(message: ProgressMessageArguments): ProgressMessage | undefined {
this.logger.info(message.text);
return undefined;
}}

@injectable()
export class DispatchingMessageClient extends MessageClient {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/common/message-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
********************************************************************************/

import { injectable, inject } from 'inversify';
import { MessageClient, MessageType, MessageOptions } from './message-service-protocol';
import { MessageClient, MessageType, MessageOptions, ProgressMessage } from './message-service-protocol';

@injectable()
export class MessageService {
Expand Down Expand Up @@ -52,6 +52,10 @@ export class MessageService {
return this.processMessage(MessageType.Error, message, args);
}

getOrCreateProgressMessage(message: string, ...actions: string[]): ProgressMessage | undefined {
return this.client.getOrCreateProgressMessage({ text: message, actions });
}

// tslint:disable-next-line:no-any
protected processMessage(type: MessageType, text: string, args?: any[]): Promise<string | undefined> {
if (!!args && args.length > 0) {
Expand Down
43 changes: 30 additions & 13 deletions packages/messages/src/browser/notifications-message-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

import { injectable, inject } from 'inversify';
import {
Message,
MessageClient,
MessageType,
Message
ProgressMessage,
ProgressMessageArguments
} from '@theia/core/lib/common';
import { Notifications, NotificationAction } from './notifications';
import { NotificationAction, NotificationProperties, Notifications } from './notifications';
import { NotificationPreferences } from './notification-preferences';

@injectable()
Expand All @@ -33,26 +35,42 @@ export class NotificationsMessageClient extends MessageClient {
return this.show(message);
}

getOrCreateProgressMessage(message: ProgressMessageArguments): ProgressMessage | undefined {
const messageArguments = {type: MessageType.Progress, text: message.text, options: { timeout: 0 }, actions: message.actions};
const key = this.getKey(messageArguments);
if (this.visibleProgressMessages.has(key)) {
return this.visibleProgressMessages.get(key);
}
const progressNotification = this.notifications.create(this.getNotificationProperties(
messageArguments,
() => {
this.visibleProgressMessages.delete(key);
}));
this.visibleProgressMessages.set(key, progressNotification);
return progressNotification;
}

protected visibleMessages = new Set<string>();
protected visibleProgressMessages = new Map<string, ProgressMessage>();
protected show(message: Message): Promise<string | undefined> {
const key = this.getKey(message);
if (this.visibleMessages.has(key)) {
return Promise.resolve(undefined);
}
this.visibleMessages.add(key);
return new Promise(resolve => {
this.showToast(message, a => {
this.notifications.show(this.getNotificationProperties(message, a => {
this.visibleMessages.delete(key);
resolve(a);
});
}));
});
}

protected getKey(m: Message): string {
return `${m.type}-${m.text}-${m.actions ? m.actions.join('|') : '|'}`;
}

protected showToast(message: Message, onCloseFn: (action: string | undefined) => void): void {
protected getNotificationProperties(message: Message, onCloseFn: (action: string | undefined) => void): NotificationProperties {
const icon = this.iconFor(message.type);
const text = message.text;
const actions = (message.actions || []).map(action => <NotificationAction>{
Expand All @@ -69,21 +87,20 @@ export class NotificationsMessageClient extends MessageClient {
label: 'Close',
fn: element => onCloseFn(undefined)
});
this.notifications.show({
return {
icon,
text,
actions,
timeout
});
};
}

protected iconFor(type: MessageType): string {
if (type === MessageType.Error) {
return 'error';
}
if (type === MessageType.Warning) {
return 'warning';
switch (type) {
case MessageType.Error: return 'error';
case MessageType.Warning: return 'warning';
case MessageType.Progress: return 'progress';
default: return 'info';
}
return 'info';
}
}
103 changes: 96 additions & 7 deletions packages/messages/src/browser/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { Emitter, Event, ProgressMessage } from '@theia/core';

export const NOTIFICATIONS_CONTAINER = 'theia-NotificationsContainer';
export const NOTIFICATION = 'theia-Notification';
Expand Down Expand Up @@ -41,6 +42,9 @@ export class Notifications {

protected container: Element;

private readonly onCancelEmitter: Emitter<void> = new Emitter<void>();
private readonly oncCancel: Event<void> = this.onCancelEmitter.event;

constructor(protected parent?: Element) {
this.parent = parent || document.body;
this.container = this.createNotificationsContainer(this.parent);
Expand All @@ -51,6 +55,10 @@ export class Notifications {
this.container.appendChild(notificationElement);
}

create(properties: NotificationProperties): ProgressMessage {
return new ProgressNotification(this.container, this.createNotificationElement(properties), this.oncCancel, properties);
}

protected createNotificationsContainer(parentContainer: Element): Element {
const container = document.createElement('div');
container.classList.add(NOTIFICATIONS_CONTAINER);
Expand All @@ -61,18 +69,34 @@ export class Notifications {
const fragment = document.createDocumentFragment();
const element = fragment.appendChild(document.createElement('div'));
element.classList.add(NOTIFICATION);
element.id = 'notification-container-' + properties.text;
const iconContainer = element.appendChild(document.createElement('div'));
iconContainer.classList.add(ICON);
const icon = iconContainer.appendChild(document.createElement('i'));
icon.classList.add('fa', this.toIconClass(properties.icon), 'fa-fw', properties.icon);
icon.classList.add(
'fa',
this.toIconClass(properties.icon),
);
if (properties.icon === 'progress') {
icon.classList.add('fa-pulse');
}
icon.classList.add(
'fa-fw',
properties.icon
);
const textContainer = element.appendChild(document.createElement('div'));
textContainer.classList.add(TEXT);
const text = textContainer.appendChild(document.createElement('p'));
text.id = 'notification-text-' + properties.text;
text.innerText = properties.text;
const handler = <Notification>{ element, properties };
const close = () => {
element.remove();
const actions = properties.actions;
if (actions) {
actions.filter(action => action.label === 'Close').forEach(action => action.fn(<Notification>{ element, properties }));
}
};
const handler = <Notification>{ element, properties };
const buttons = element.appendChild(document.createElement('div'));
buttons.classList.add(BUTTONS);

Expand All @@ -91,20 +115,85 @@ export class Notifications {
}
action.fn(handler);
close();
this.onCancelEmitter.fire(undefined);
});
}
}
return fragment;
}

protected toIconClass(icon: string): string {
if (icon === 'error') {
return 'fa-times-circle';
switch (icon) {
case 'error': return 'fa-times-circle';
case 'Warning': return 'fa-warning';
case 'progress': return 'fa-spinner';
default: return 'fa-info-circle';
}
}

}

class ProgressNotification implements ProgressMessage {
private increment: number = 0;
private readonly node: Node;
private readonly container: Element;
private readonly properties: NotificationProperties;

readonly onCancel: Event<void>;

constructor(container: Element, node: Node, oncCancel: Event<void>, properties: NotificationProperties) {
this.node = node;
this.onCancel = oncCancel;
this.container = container;
this.properties = properties;
}

close(): void {
const element = document.getElementById('notification-container-' + this.properties.text);
if (!element) {
return;
}
element.remove();
const actions = this.properties.actions;
if (!actions) {
return;
}
actions.filter(action => action.label === 'Close')
.forEach(action => action.fn(
<Notification>{
element,
properties: this.properties
})
);
}

show(): void {
let container = document.getElementById('notification-container-' + this.properties.text);
if (!container) {
this.container.appendChild(this.node);
}
if (icon === 'warning') {
return 'fa-warning';
container = document.getElementById('notification-container-' + this.properties.text);
if (container) {
const progressContainer = container.appendChild(document.createElement('div'));
progressContainer.className = 'progress';
const progress = progressContainer.appendChild(document.createElement('p'));
progress.id = 'notification-progress-' + this.properties.text;
}
return 'fa-info-circle';
}

update(item: { message?: string, increment?: number }): void {
const textElement = document.getElementById('notification-text-' + this.properties.text);
if (textElement) {
if (item.increment) {
this.increment = this.increment + item.increment;
this.increment = this.increment > 100 ? 100 : this.increment;

const progressElement = document.getElementById('notification-progress-' + this.properties.text);
if (progressElement) {
progressElement.innerText = this.increment + '%';
}
}
textElement.innerText = this.properties.text + (item.message ? ': ' + item.message : '');
}
}
}
22 changes: 20 additions & 2 deletions packages/messages/src/browser/style/notifications.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,26 @@
color: var(--theia-warn-color0);
}

.theia-Notification .text {
.theia-Notification .progress {
order: 2;
width: 35px;
align-items: center;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
align-self: center;
height: 100%;
}

.theia-Notification .progress > p {
margin: 0px;
font-size: var(--theia-ui-font-size1);
vertical-align: middle;
}

.theia-Notification .text {
order: 3;
display: flex;
align-items: center;
justify-items: left;
Expand All @@ -104,7 +122,7 @@
.theia-Notification .buttons {
display: flex;
flex-direction: row;
order: 3;
order: 4;
white-space: nowrap;
align-self: flex-end;
height: 40px;
Expand Down
Loading

0 comments on commit ea443a6

Please sign in to comment.