Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feature(electron): initial functionality working
Browse files Browse the repository at this point in the history
  • Loading branch information
robwormald committed Feb 13, 2016
1 parent eff42f5 commit 6e0775e
Show file tree
Hide file tree
Showing 21 changed files with 563 additions and 6 deletions.
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# http://editorconfig.org

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

[*.json]
insert_final_newline = false
1 change: 1 addition & 0 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
### Angular Electron Sample App
6 changes: 6 additions & 0 deletions demo/app_ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'reflect-metadata';
import {bootstrapElectronRenderer} from '../dist/renderer';

bootstrapElectronRenderer();


16 changes: 16 additions & 0 deletions demo/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Component} from 'angular2/core';

@Component({
selector: 'app',
template: `<div>Hello from {{name}}</div>`
})
export class App {
name: string;
constructor(){
this.name = 'Angular2 Electron!';

setTimeout(() => {
this.name = 'Angular2 Electron!!!';
},1000);
}
}
9 changes: 9 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<script src="../node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="app_ui.js"></script>
</head>
<body>
<app>Loading...</app>
</body>
</html>
7 changes: 7 additions & 0 deletions demo/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'reflect-metadata';
import 'zone.js/dist/zone-microtask'
import {bootstrap} from '../dist/main';

import {App} from './component';

bootstrap(App, []);
5 changes: 5 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name" : "angular-electron-demo",
"version" : "1.0.0",
"main" : "main.js"
}
18 changes: 16 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"description": "Electron Platform for Angular2",
"main": "index.js",
"scripts": {
"test": "test"
"build": "npm run clean && tsc",
"build_demo": "tsc typings/browser.d.ts demo/main.ts demo/app_ui.ts --module commonjs -t es5 --experimentalDecorators --emitDecoratorMetadata",
"clean": "rm -rf dist",
"test": "test",
"demo": "npm run build && npm run build_demo && electron ./demo"
},
"repository": {
"type": "git",
Expand All @@ -19,5 +23,15 @@
"bugs": {
"url": "https://github.com/angular/angular-electron/issues"
},
"homepage": "https://github.com/angular/angular-electron#readme"
"homepage": "https://github.com/angular/angular-electron#readme",
"devDependencies": {
"electron-prebuilt": "^0.36.7",
"typescript": "^1.7.5"
},
"peerDependencies": {
"angular2": "2.0.0-beta.6"
},
"dependencies": {
"parse5": "^1.5.1"
}
}
8 changes: 8 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//public angular electron API for main (node/background)
export * from 'angular2/common';
export * from 'angular2/core';
export * from 'angular2/platform/worker_app';
export {UrlResolver} from 'angular2/compiler';
export * from 'angular2/instrumentation';
export * from './platform/electron_app';
export * from './platform/electron';
4 changes: 4 additions & 0 deletions src/platform/electron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {platform} from 'angular2/core';
import { ELECTRON_APP_APPLICATION, bootstrap } from './electron_app';
import { ELECTRON_APP_PLATFORM } from './electron_app_common';
export { bootstrap }
67 changes: 67 additions & 0 deletions src/platform/electron_app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as electron from 'electron';
import {ElectronMessageBus, ElectronMessageBusSink, ElectronMessageBusSource, ELECTRON_READY} from './electron_message_bus';
import {ELECTRON_APP_APPLICATION_COMMON, ELECTRON_APP_PLATFORM} from './electron_app_common';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {Type, CONST_EXPR, isPresent} from 'angular2/src/facade/lang';
import {Provider} from 'angular2/src/core/di';
import {Parse5DomAdapter} from 'angular2/src/platform/server/parse5_adapter';
import {APP_INITIALIZER, platform, ComponentRef} from 'angular2/core';
import {MessageBus} from 'angular2/src/web_workers/shared/message_bus';
import {COMPILER_PROVIDERS} from 'angular2/src/compiler/compiler';

export const ELECTRON_APP_APPLICATION: Array<any /*Type | Provider | any[]*/> = [
ELECTRON_APP_APPLICATION_COMMON,
COMPILER_PROVIDERS,
new Provider(MessageBus, { useFactory: createMessageBus, deps: [NgZone] }),
new Provider(APP_INITIALIZER, { useValue: () => {}, multi: true })
];

let applicationRef:Electron.BrowserWindow;

function createMessageBus(zone: NgZone): MessageBus {
let sink = new ElectronMessageBusSink(applicationRef.webContents);
let source = new ElectronMessageBusSource(electron.ipcMain);
let bus = new ElectronMessageBus(sink, source);
bus.attachToZone(zone);
return bus;
}

function waitForAppReady(){
return new Promise((resolve, reject) => {
electron.app.on('ready', resolve);
});
}

function waitForPingback(){
initializeMainWindow()
return new Promise((resolve) => {
electron.ipcMain.once(ELECTRON_READY, (ev) => {
ev.returnValue = 'ok';
resolve();
});
});
}

function initializeMainWindow(){
applicationRef = new electron.BrowserWindow();
applicationRef.loadURL(`file://${process.cwd()}/demo/index.html`);
}

export function bootstrap(appComp, providers?:any) {
Parse5DomAdapter.makeCurrent();

return platform([ ELECTRON_APP_PLATFORM ])
.asyncApplication((z) => {
return z.run(() => {
return waitForAppReady()
.then(waitForPingback)
.then(() => {
return ELECTRON_APP_APPLICATION;
})
})
})
.then((appRef) => {
return appRef.bootstrap(appComp);
})

}
56 changes: 56 additions & 0 deletions src/platform/electron_app_common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {XHR} from 'angular2/src/compiler/xhr';
import {WebWorkerXHRImpl} from 'angular2/src/web_workers/worker/xhr_impl';
import {WebWorkerRootRenderer} from 'angular2/src/web_workers/worker/renderer';
import {print, Type, CONST_EXPR, isPresent} from 'angular2/src/facade/lang';
import {RootRenderer} from 'angular2/src/core/render/api';
import {
PLATFORM_DIRECTIVES,
PLATFORM_PIPES,
ExceptionHandler,
APPLICATION_COMMON_PROVIDERS,
PLATFORM_COMMON_PROVIDERS,
} from 'angular2/core';
import {COMMON_DIRECTIVES, COMMON_PIPES, FORM_PROVIDERS} from "angular2/common";
import {
ClientMessageBrokerFactory,
ClientMessageBrokerFactory_
} from 'angular2/src/web_workers/shared/client_message_broker';
import {
ServiceMessageBrokerFactory,
ServiceMessageBrokerFactory_
} from 'angular2/src/web_workers/shared/service_message_broker';
import {Serializer} from "angular2/src/web_workers/shared/serializer";
import {ON_WEB_WORKER} from "angular2/src/web_workers/shared/api";
import {Provider} from 'angular2/src/core/di';
import {RenderStore} from 'angular2/src/web_workers/shared/render_store';

class PrintLogger {
log = print;
logError = print;
logGroup = print;
logGroupEnd() {}
}

export const ELECTRON_APP_PLATFORM: Array<any /*Type | Provider | any[]*/> =
CONST_EXPR([PLATFORM_COMMON_PROVIDERS]);

export const ELECTRON_APP_APPLICATION_COMMON: Array<any /*Type | Provider | any[]*/> = CONST_EXPR([
APPLICATION_COMMON_PROVIDERS,
FORM_PROVIDERS,
Serializer,
new Provider(PLATFORM_PIPES, {useValue: COMMON_PIPES, multi: true}),
new Provider(PLATFORM_DIRECTIVES, {useValue: COMMON_DIRECTIVES, multi: true}),
new Provider(ClientMessageBrokerFactory, {useClass: ClientMessageBrokerFactory_}),
new Provider(ServiceMessageBrokerFactory, {useClass: ServiceMessageBrokerFactory_}),
WebWorkerRootRenderer,
new Provider(RootRenderer, {useExisting: WebWorkerRootRenderer}),
new Provider(ON_WEB_WORKER, {useValue: true}),
RenderStore,
new Provider(ExceptionHandler, {useFactory: _exceptionHandler, deps: []}),
WebWorkerXHRImpl,
new Provider(XHR, {useExisting: WebWorkerXHRImpl})
]);

function _exceptionHandler(): ExceptionHandler {
return new ExceptionHandler(new PrintLogger());
}
141 changes: 141 additions & 0 deletions src/platform/electron_message_bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as electron from 'electron';
import {
MessageBus,
MessageBusSource,
MessageBusSink
} from "angular2/src/web_workers/shared/message_bus";
import {NgZone, EventEmitter, Injectable} from 'angular2/core';

/**
* Typescript Implementation of MessageBus for use in electron apps
*/

export const ELECTRON_WORKER = '__ELECTRON_WORKER';
export const ELECTRON_CLIENT = '__ELECTRON_CLIENT';
export const ELECTRON_READY = '__ELECTRON_READY'

const ELECTRON_CHANNEL = '__ELECTRON_CHANNEL';

@Injectable()
export class ElectronMessageBus implements MessageBus {
constructor(public sink: ElectronMessageBusSink,
public source: ElectronMessageBusSource,
private env: string = ELECTRON_CLIENT) {}

attachToZone(zone: NgZone): void {
this.source.attachToZone(zone);
this.sink.attachToZone(zone);
}

initChannel(channel: string, runInZone: boolean = false): void {
this.source.initChannel(channel, runInZone);
this.sink.initChannel(channel, runInZone);
}

from(channel: string): EventEmitter<any> { return this.source.from(channel); }

to(channel: string): EventEmitter<any> { return this.sink.to(channel); }
}

export class ElectronMessageBusSink implements MessageBusSink {
private _zone: NgZone;
private _channels: Map<string, _ElectronMessageChannel> =
new Map<string, _ElectronMessageChannel>();
private _messageBuffer: Array<Object> = [];

constructor(private _ipc: any) {}

attachToZone(zone: NgZone): void {
this._zone = zone;
this._zone.onTurnDone.subscribe(() => {this._handleOnEventDone()});
}

initChannel(channel: string, runInZone: boolean = true): void {
if (this._channels.has(channel)) {
throw new Error(`${channel} has already been initialized`);
}
let _channel = new _ElectronMessageChannel(new EventEmitter(), runInZone);
this._channels.set(channel, _channel);
_channel.emitter.subscribe((data: any) => {
var message = {channel : channel, message : data};

if (runInZone) {
this._messageBuffer.push(message);
} else {
this._sendMessages([ message ]);
}
});
}

to(channel: string): EventEmitter<any> {
if (!this._channels.has(channel)) {
throw new Error(`${channel} does not exist!`);
}
return this._channels.get(channel).emitter;
}

private _sendMessages(messages: any[]) {
if (this._ipc.sendChannel) {
this._ipc.sendChannel(ELECTRON_CHANNEL, messages);
} else {
this._ipc.send(ELECTRON_CHANNEL, messages);
}
}
private _handleOnEventDone() {
if (this._messageBuffer.length > 0) {
this._sendMessages(this._messageBuffer);
this._messageBuffer = [];
}
}
}

export class ElectronMessageBusSource implements MessageBusSource {
private _zone: NgZone;
private _channels: Map<string, _ElectronMessageChannel> =
new Map<string, _ElectronMessageChannel>();

constructor(private _ipc?: any) {
this._ipc.on(ELECTRON_CHANNEL, (ev, data) => this._handleMessages(data || ev));
}
attachToZone(zone: NgZone) { this._zone = zone; }

initChannel(channel: string, runInZone: boolean = true) {

if (this._channels.has(channel)) {
throw new Error(`${channel} has already been initialized`);
}

let emitter = new EventEmitter();
let channelInfo = new _ElectronMessageChannel(emitter, runInZone);
this._channels.set(channel, channelInfo);
}

from(channel: string): EventEmitter<any> {
if (this._channels.has(channel)) {
return this._channels.get(channel).emitter;
} else {
throw new Error(
`${channel} is not set up. Did you forget to call initChannel?`);
}
}

private _handleMessages(messages: any[]): void {
for (var i = 0; i < messages.length; i++) {
this._handleMessage(messages[i]);
}
}

private _handleMessage(data: any): void {
var channel = data.channel;
if (this._channels.has(channel)) {
var channelInfo = this._channels.get(channel);
this._zone.run(() => { channelInfo.emitter.next(data.message); });
} else {
throw new Error('unhandled message!');
}
}
}

class _ElectronMessageChannel {
constructor(public emitter: EventEmitter<any>, public runInZone: boolean) {}
}
Loading

0 comments on commit 6e0775e

Please sign in to comment.