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

Add notification manager API #2

Merged
merged 1 commit into from
May 7, 2018
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
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