Skip to content
This repository has been archived by the owner on Jun 20, 2018. It is now read-only.

Commit

Permalink
Add notification manager API (#2)
Browse files Browse the repository at this point in the history
Signed-off-by: Oleksii Orel <oorel@redhat.com>
  • Loading branch information
olexii4 authored and benoitf committed Jun 18, 2018
1 parent 2435c01 commit 03d7f24
Show file tree
Hide file tree
Showing 12 changed files with 473 additions and 36 deletions.
15 changes: 14 additions & 1 deletion packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ export interface PickOpenItem {
detail?: string;
picked?: boolean;
}

export interface MessageRegistryMain {
$showInformationMessage (message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined>;
$showWarningMessage (message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined>;
$showErrorMessage (message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined>;
}
export interface QuickOpenExt {
$onItemSelected(handle: number): void;
$validateInput(input: string): PromiseLike<string> | undefined;
Expand All @@ -70,7 +82,8 @@ export interface QuickOpenMain {

export const PLUGIN_RPC_CONTEXT = {
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>('CommandRegistryMain'),
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain')
QUICK_OPEN_MAIN: createProxyIdentifier<QuickOpenMain>('QuickOpenMain'),
MESSAGE_REGISTRY_MAIN: <ProxyIdentifier<MessageRegistryMain>>createProxyIdentifier<MessageRegistryMain>('MessageRegistryMain')
};

export const MAIN_RPC_CONTEXT = {
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { createAPI, startPlugin } from '../../../plugin/plugin-context';
const ctx = self as any;
const plugins = new Map<string, () => void>();

const emmitter = new Emitter();
const emitter = new Emitter();
const rpc = new RPCProtocolImpl({
onMessage: emmitter.event,
onMessage: emitter.event,
send: (m: {}) => {
ctx.postMessage(m);
}
});
addEventListener('message', (message: any) => {
emmitter.fire(message.data);
emitter.fire(message.data);
});

const theia = createAPI(rpc);
Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-ext/src/hosted/node/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ export class HostedPluginSupport {
}

private terminatePluginServer(cp: cp.ChildProcess) {
const emmitter = new Emitter();
const emitter = new Emitter();
cp.on('message', message => {
emmitter.fire(JSON.parse(message));
emitter.fire(JSON.parse(message));
});
const rpc = new RPCProtocolImpl({
onMessage: emmitter.event,
onMessage: emitter.event,
send: (m: {}) => {
if (cp.send) {
cp.send(JSON.stringify(m));
Expand Down
81 changes: 81 additions & 0 deletions packages/plugin-ext/src/main/browser/dialogs/modal-notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {injectable} from 'inversify';
import {Message} from '@phosphor/messaging';
import {Key} from '@theia/core/lib/browser';
import {MessageType} from '../message-registry-main';
import {AbstractDialog} from '@theia/core/lib/browser/dialogs';
import '../../../../src/main/browser/dialogs/style/modal-notification.css';

const NOTIFICATION = 'theia-Notification';
const ICON = 'icon';
const TEXT = 'text';

@injectable()
export class ModalNotification extends AbstractDialog<string | undefined> {

protected actionTitle: string | undefined;

constructor() {
super({title: 'Theia'});
}

protected onCloseRequest(msg: Message): void {
this.actionTitle = undefined;
this.accept();
}

get value(): string | undefined {
return this.actionTitle;
}

showDialog(messageType: MessageType, text: string, actions: string[]): Promise<string | undefined> {
this.contentNode.appendChild(this.createMessageNode(messageType, text, actions));
return this.open();
}

protected createMessageNode(messageType: MessageType, text: string, actions: string[]): HTMLElement {
const messageNode = document.createElement('div');
messageNode.classList.add(NOTIFICATION);

const iconContainer = messageNode.appendChild(document.createElement('div'));
iconContainer.classList.add(ICON);
const iconElement = iconContainer.appendChild(document.createElement('i'));
iconElement.classList.add('fa', this.toIconClass(messageType), 'fa-fw', messageType);

const textContainer = messageNode.appendChild(document.createElement('div'));
textContainer.classList.add(TEXT);
const textElement = textContainer.appendChild(document.createElement('span'));
textElement.textContent = text;

actions.forEach((action: string) => {
const button = this.createButton(action);
button.classList.add('main');
this.controlPanel.appendChild(button);
this.addKeyListener(button,
Key.ENTER,
() => {
this.actionTitle = action;
this.accept();
},
'click');
});
this.appendCloseButton('close');

return messageNode;
}

protected toIconClass(icon: string): string {
if (icon === MessageType.Error) {
return 'fa-times-circle';
}
if (icon === MessageType.Warning) {
return 'fa-warning';
}
return 'fa-info-circle';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
.dialogContent .theia-Notification {
min-width: inherit;
box-shadow: none;
animation: none;
}

.dialogContent .theia-Notification .icon {
font-size: 20px;
padding: 5px 0;
}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import { CommandRegistryMainImpl } from './command-registry-main';
import { QuickOpenMainImpl } from './quick-open-main';
import { RPCProtocol } from '../../api/rpc-protocol';
import { PLUGIN_RPC_CONTEXT } from '../../api/plugin-api';
import { MessageRegistryMainImpl } from './message-registry-main';

export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
const commandRegistryMain = new CommandRegistryMainImpl(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.COMMAND_REGISTRY_MAIN, commandRegistryMain);

const quickOpenMain = new QuickOpenMainImpl(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.QUICK_OPEN_MAIN, quickOpenMain);

const messageRegistryMain = new MessageRegistryMainImpl(container);
rpc.set(PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN, messageRegistryMain);
}
104 changes: 104 additions & 0 deletions packages/plugin-ext/src/main/browser/message-registry-main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {interfaces} from 'inversify';
import * as theia from '@theia/plugin';
import {MessageService} from '@theia/core/lib/common/message-service';
import {MessageRegistryMain} from '../../api/plugin-api';
import {ModalNotification} from './dialogs/modal-notification';

export enum MessageType {
Error = 'error',
Warning = 'warning',
Info = 'info'
}

export class MessageRegistryMainImpl implements MessageRegistryMain {
private messageService: MessageService;

constructor(container: interfaces.Container) {
this.messageService = container.get(MessageService);
}

$showInformationMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined> {
return this.showMessage(MessageType.Info, message, optionsOrFirstItem, ...items);
}

$showWarningMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined> {
return this.showMessage(MessageType.Warning, message, optionsOrFirstItem, ...items);
}

$showErrorMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
items: string[] | theia.MessageItem[]): PromiseLike<string | theia.MessageItem | undefined> {
return this.showMessage(MessageType.Error, message, optionsOrFirstItem, ...items);
}

protected showMessage(type: MessageType, message: string, ...args: any[]): PromiseLike<string | theia.MessageItem | undefined> {
const actionsMap = new Map<string, any>();
const actionTitles: string[] = [];
const options: theia.MessageOptions = {modal: false};

let onCloseAction: string;
if (!!args && args.length > 0) {
const first = args[0];
if (first && first.modal) {
options.modal = true;
}
args.forEach(arg => {
if (!arg) {
return;
}
let actionTitle: string;
if (typeof arg === 'string') {
actionTitle = arg;
} else if (arg.title) {
actionTitle = arg.title;
actionsMap.set(actionTitle, arg);
if (arg.isCloseAffordance) {
onCloseAction = arg.title;
}
} else {
return;
}
actionTitles.push(actionTitle);
});
}

let promise: Promise<string | undefined>;

try {
if (options.modal) {
const modalNotification = new ModalNotification();
promise = modalNotification.showDialog(type, message, actionTitles).then(result => {
return result !== undefined ? result : onCloseAction;
});
} else {
switch (type) {
case MessageType.Info:
promise = this.messageService.info(message, ...actionTitles);
break;
case MessageType.Warning:
promise = this.messageService.warn(message, ...actionTitles);
break;
case MessageType.Error:
promise = this.messageService.error(message, ...actionTitles);
break;
default:
return Promise.reject(new Error(`Message type '${type}' is not supported yet!`));
}
}
} catch (e) {
return Promise.reject(e);
}

return Promise.resolve(promise.then(result => !!result && actionsMap.has(result) ? actionsMap.get(result) : result));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import { HostedPluginManagerClient } from "./plugin-manager-client";
import { PluginApiFrontendContribution } from "./plugin-frontend-contribution";
import { setUpPluginApi } from "./main-context";
import { HostedPluginServer, hostedServicePath } from "../../common/plugin-protocol";
import { ModalNotification } from './dialogs/modal-notification';

export default new ContainerModule(bind => {
bind(ModalNotification).toSelf().inSingletonScope();

bind(PluginWorker).toSelf().inSingletonScope();
bind(HostedPluginSupport).toSelf().inSingletonScope();
bind(HostedPluginWatcher).toSelf().inSingletonScope();
Expand Down
38 changes: 38 additions & 0 deletions packages/plugin-ext/src/plugin/message-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import {
PLUGIN_RPC_CONTEXT as Ext, MessageRegistryMain
} from '../api/plugin-api';
import {RPCProtocol} from '../api/rpc-protocol';
import {MessageItem, MessageOptions} from "@theia/plugin";

export class MessageRegistryExt {

private proxy: MessageRegistryMain;

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(Ext.MESSAGE_REGISTRY_MAIN);
}

showInformationMessage(message: string,
optionsOrFirstItem: MessageOptions | string | MessageItem,
items: string[] | MessageItem[]): PromiseLike<string | MessageItem | undefined> {
return this.proxy.$showInformationMessage(message, optionsOrFirstItem, items);
}

showWarningMessage(message: string,
optionsOrFirstItem: MessageOptions | string | MessageItem,
items: string[] | MessageItem[]): PromiseLike<string | MessageItem | undefined> {
return this.proxy.$showWarningMessage(message, optionsOrFirstItem, items);
}

showErrorMessage(message: string,
optionsOrFirstItem: MessageOptions | string | MessageItem,
items: string[] | MessageItem[]): PromiseLike<string | MessageItem | undefined> {
return this.proxy.$showErrorMessage(message, optionsOrFirstItem, items);
}
}
17 changes: 17 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import { MAIN_RPC_CONTEXT, Plugin } from '../api/plugin-api';
import { RPCProtocol } from '../api/rpc-protocol';
import { getPluginId } from '../common/plugin-protocol';
import { Disposable } from './types-impl';
import { MessageRegistryExt } from './message-registry';

export function createAPI(rpc: RPCProtocol): typeof theia {
const commandRegistryExt = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc));
const quickOpenExt = rpc.set(MAIN_RPC_CONTEXT.QUICK_OPEN_EXT, new QuickOpenExtImpl(rpc));
const messageRegistryExt = new MessageRegistryExt(rpc);

const commands: typeof theia.commands = {
registerCommand(command: theia.Command, handler?: <T>(...args: any[]) => T | Thenable<T>): Disposable {
Expand All @@ -36,6 +38,21 @@ export function createAPI(rpc: RPCProtocol): typeof theia {
const window: typeof theia.window = {
showQuickPick(items: any, options: theia.QuickPickOptions, token?: theia.CancellationToken): any {
return quickOpenExt.showQuickPick(items, options, token);
},
showInformationMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
...items: any[]): PromiseLike<any> {
return messageRegistryExt.showInformationMessage(message, optionsOrFirstItem, items);
},
showWarningMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
...items: any[]): PromiseLike<any> {
return messageRegistryExt.showWarningMessage(message, optionsOrFirstItem, items);
},
showErrorMessage(message: string,
optionsOrFirstItem: theia.MessageOptions | string | theia.MessageItem,
...items: any[]): PromiseLike<any> {
return messageRegistryExt.showErrorMessage(message, optionsOrFirstItem, items);
}
};

Expand Down
19 changes: 19 additions & 0 deletions packages/plugin/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,22 @@ theia.window.showQuickPick(["foo", "bar", "foobar"], option).then((val: string[]
console.log(`Quick Pick Selected: ${val}`);
});
```
#### Notification API
A notification shows an information message to users.
Optionally provide an array of items which will be presented as clickable buttons.

Notifications can be shown using the [showInformationMessage](#window.showInformationMessage),
[showWarningMessage](#window.showWarningMessage) and [showErrorMessage](#window.showErrorMessage) functions.


Simple example that show an information message:
```javascript
theia.window.showInformationMessage('Information message');
```

Simple example that show an information message with buttons:
```javascript
theia.window.showInformationMessage('Information message', 'Btn1', 'Btn2').then(result => {
console.log("Click button", result);
});
```
Loading

0 comments on commit 03d7f24

Please sign in to comment.