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

Render service #55

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"dependencies": {
"angular2": "2.0.0-beta.7",
"angular2-universal-preview": "0.55.4",
"angularfire2": "2.0.0-alpha.9",
"angularfire2": "2.0.0-alpha.13",
"css": "2.2.1",
"es6-promise": "3.1.2",
"es6-shim": "0.33.13",
Expand Down Expand Up @@ -47,6 +47,6 @@
"ts-node": "0.5.4",
"typescript": "1.7.5",
"typescript-node": "0.1.3",
"typings": "^0.6.8"
"typings": "^0.6.9"
}
}
4 changes: 4 additions & 0 deletions src/css/core/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@
margin: 0;
color: #FFF;
text-decoration: none;
padding-right: 20px;
&:last-child {
padding-right: 0;
}
}
2 changes: 2 additions & 0 deletions src/index.ng2.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<body>
<app></app>

<!-- TODO(jteplitz602): Add this to vendor bundle -->
<script src="//cdn.ckeditor.com/4.5.7/standard/ckeditor.js"></script>
<script src="/angular2-polyfills.js"></script>
<script src="/system.js"></script>
<script src="/vendor_ui.js"></script>
Expand Down
2 changes: 2 additions & 0 deletions src/main-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ng2engine,
REQUEST_URL
} from 'angular2-universal-preview/dist/server';
import {FIREBASE_PROVIDERS} from 'angularfire2';

import {provide} from 'angular2/core';
import {APP_BASE_HREF, ROUTER_PROVIDERS} from 'angular2/router';
Expand All @@ -32,6 +33,7 @@ app.use('/', (req, res) => {
res.render('index', { App, providers: [
ROUTER_PROVIDERS,
SERVER_LOCATION_PROVIDERS,
FIREBASE_PROVIDERS,
provide(REQUEST_URL, {useValue: req.originalUrl}),
provide(APP_BASE_HREF, {useValue: `http://localhost:3000${req.baseUrl}`}),
provide(REQUEST_URL, {useValue: 'http://localhost:3000'}),
Expand Down
18 changes: 14 additions & 4 deletions src/shared-providers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import {provide} from 'angular2/core';
import {FIREBASE_PROVIDERS, defaultFirebase} from 'angularfire2';
import {
defaultFirebase,
firebaseAuthConfig,
AuthMethods,
AuthProviders
} from 'angularfire2';

import {AuthService} from './worker/services/Auth';
import {Backend, BackendConfig} from './worker/services/Backend';
import {QuestionService} from './worker/services/QuestionService';

import {AnswerService} from './worker/services/AnswerService';

export const SHARED_PROVIDERS = [
AuthService,
QuestionService,
FIREBASE_PROVIDERS,
defaultFirebase('answers-mobile.firebaseio.com')
AnswerService,
defaultFirebase('answers-mobile.firebaseio.com'),
firebaseAuthConfig({
method: AuthMethods.Redirect,
provider: AuthProviders.Github
})
];

37 changes: 37 additions & 0 deletions src/shared/ckeditor_renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {RenderService} from './render_service';
import {Injectable, ElementRef, Injector} from 'angular2/core';

declare var CKEDITOR: any;

@RenderService({
methods: [{
name: 'init',
args: [ElementRef],
returnType: boolean
}, {
name: 'destroy',
args: [ElementRef]
}]
})
@Injectable()
export class CKEditorRenderer {
private _editors = new Map<ElementRef, any>();
// NB: For now all RenderServices MUST have a public injector member
// This is used by the RenderService annotation to inject things like the the MessageBroker
constructor (public injector: Injector) {}
init (elem): Promise<boolean> {
let editor = CKEDITOR.replace(elem);
editor.on("change", (e) => {
elem.value = e.editor.getData();
let event = new Event('change');
elem.dispatchEvent(event);
});
this._editors.set(elem, editor);
}

destroy(elem) {
let editor = this._editors.get(elem);
editor.destroy();
this._editors.delete(elem);
}
}
100 changes: 100 additions & 0 deletions src/shared/render_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
FnArg,
ClientMessageBroker,
ClientMessageBrokerFactory,
ServiceMessageBrokerFactory,
UiArguments,
PRIMITIVE
} from 'angular2/platform/worker_app';
import {RenderStoreObject} from 'angular2/src/web_workers/shared/serializer';
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';

declare var WorkerGlobalScope;

let num = 1;
let obj = {};
let serializationMap = {};
serializationMap[ElementRef.toString()] = RenderStoreObject;
serializationMap[obj.constructor.toString()] = PRIMITIVE;
serializationMap[[].constructor.toString()] = PRIMITIVE;
serializationMap[false.constructor.toString()] = PRIMITIVE;
serializationMap["".constructor.toString()] = PRIMITIVE;
serializationMap[num.constructor.toString()] = PRIMITIVE;

export interface MethodMetadata {
name: string,
args: [Function],
returnType?: Function
}

export interface RenderServiceMetadata {
methods: [MethodMetadata]
}

export function RenderService (metadata: RenderServiceMetadata) {
return function(target) {
let channelName = target.toString()
if (isWorker()) {
let state = {
channelName: channelName,
broker: null
};
var nativeMethods = {};
for (var i = 0; i < metadata.methods.length; i++) {
let method = metadata.methods[i];
patchMethod(target, method, state);
}
} else {
target.prototype.ngStartListening = function() {
let brokerFactory = this.injector.get(ServiceMessageBrokerFactory);
let broker = brokerFactory.createMessageBroker(channelName, true);
for (var i = 0; i < metadata.methods.length; i++) {
let methodInfo = metadata.methods[i];
let methodName = methodInfo.name;
let args = metadata.methods[i].args.map((v) =>
serializationMap[v.toString()] ? serializationMap[v.toString()] : v);
broker.registerMethod(methodName, args,
target.prototype[methodName].bind(this), methodInfo.returnType);
}
}
}
}
}

function patchMethod(target: any, methodInfo: MethodMetadata, state: ServiceState) {
target.prototype[methodInfo.name] = function() {
if (state.broker == null) {
let brokerFactory: ClientMessageBrokerFactory =
this.injector.get(ClientMessageBrokerFactory);
state.broker = brokerFactory.createMessageBroker(state.channelName, true);
}
let broker = state.broker;
let args = [];
for (var i = 0; i < arguments.length; i++) {
let val = arguments[i];
if (val) {
let type = methodInfo.args[i];
if (type == ElementRef) {
val = val.nativeElement;
}
if (serializationMap[type.toString()]) {
type = serializationMap[type.toString()];
}
args.push(new FnArg(val, type));
} else {
// val may be null, undefined, etc...
args.push(val, PRIMITIVE);
}
}
broker.runOnService(new UiArguments(methodInfo.name, args), methodInfo.returnType);
}
}

function isWorker() {
return typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
}

interface ServiceState {
channelName: string;
broker: ClientMessageBroker
}
15 changes: 15 additions & 0 deletions src/ui/main_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@ import {
} from 'angular2/platform/worker_render';
import {platform, provide} from 'angular2/core';
import {BOOTSTRAP_CHANNEL} from '../shared/channels';
import {CKEditorRenderer} from '../shared/ckeditor_renderer';
import {
defaultFirebase,
} from 'angularfire2';
import {WORKER_RENDER_FIREBASE_PROVIDERS} from 'angularfire2/angularfire2_worker_render';
// Need to import from providers directly
// until https://github.com/angular/angularfire2/pull/111 is merged
import {MessageBasedFirebaseAuth} from 'angularfire2/providers/web_workers/ui/auth';

let appRef = platform([WORKER_RENDER_PLATFORM])
.application([
WORKER_RENDER_FIREBASE_PROVIDERS,
defaultFirebase('answers-mobile.firebaseio.com'),
WORKER_RENDER_APPLICATION,
WORKER_RENDER_ROUTER,
CKEditorRenderer,
provide(WORKER_SCRIPT, {useValue: '/loader.js'})
]);

Expand All @@ -22,3 +33,7 @@ bus.from(BOOTSTRAP_CHANNEL).subscribe((message: string) => {
(<any> window).preboot.complete();
}
});

// Need to manually call start until https://github.com/angular/angular/issues/7420 is implemented
appRef.injector.get(MessageBasedFirebaseAuth).start();
appRef.injector.get(CKEditorRenderer).ngStartListening();
32 changes: 32 additions & 0 deletions src/worker/components/AnswerList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {Component, Input} from 'angular2/core';
import {ROUTER_DIRECTIVES} from 'angular2/router';
import {Answer} from '../services/AnswerService';

const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'];

@Component({
selector: 'answer-list',
template: `
<div class="card" *ngFor="#answer of answers">
<p>{{ answer.text }}</p>
<p class='username'>Answered by {{ answer.username }} on {{ timestampToDate (answer.timestamp) }}.</p>
</div>
`,
directives: [ROUTER_DIRECTIVES],
styles: [`
.username {
font-size: 12pt;
text-align: right;
}
`]
})
export class AnswerList {
@Input() answers: Answer[];

timestampToDate (timestamp: number): string {
let date = new Date(timestamp);
let month = months[date.getMonth()];
return `${month} ${date.getDate()}`;
}
}
52 changes: 52 additions & 0 deletions src/worker/components/CreateAnswer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {Component, Input, ChangeDetectionStrategy} from 'angular2/core';
import {AnswerService, Answer} from '../services/AnswerService';
import {FirebaseAuth} from 'angularfire2';

import * as Firebase from 'firebase';

@Component({
selector: 'create-answer',
template: `
<div [ngSwitch]="(auth | async)" class="card">
<span *ngSwitchWhen="null">
<h3>You must <a (click)="auth.login()">login</a> to answer this question.</h3>
</span>
<span *ngSwitchDefault>
<h3>Add Answer</h3>
<div class="new-answer-container">
<input type="text" placeholder="Answer Here" [(ngModel)]="newAnswer.text" />
</div>
<button (click)="addAnswer()">Save</button>
</span>
</div>
`,
styles: [
`.new-answer-container {
display: flex;
flex-direction: column
}`
]
})
export class CreateAnswer {
@Input() questionId: string;
newAnswer: Answer = {
timestamp: Firebase.ServerValue.TIMESTAMP,
text: '',
uid: '',
username: ''
};

constructor(private _answerService: AnswerService, public auth: FirebaseAuth){
this.auth.subscribe ((authData) => {
if (authData != null) {
this.newAnswer.uid = authData.uid;
this.newAnswer.username = authData.github.username;
}
});
}

addAnswer(){
this._answerService.addAnswer(this.questionId, this.newAnswer);
this.newAnswer.text = '';
}
}
22 changes: 19 additions & 3 deletions src/worker/components/header.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import {Component} from 'angular2/core';
import {Component, ChangeDetectionStrategy} from 'angular2/core';
import {Nav} from '../services/Nav';
import {ROUTER_DIRECTIVES} from 'angular2/router';
import {FirebaseAuth} from 'angularfire2';

@Component({
selector: 'app-header',
template: `
<img role="tab" class="header__menu js-toggle-menu" src="/images/ic_menu_24px.svg" alt="toggle_nav" (click)="openSideNav()"/>

<a class="header__title" [routerLink]="['Questions']">Angular Answers</a>
<a class="header__item" *ngIf="!(auth | async)" (click)="login()">Login</a>
<a class="header__item" *ngIf="auth | async" (click)="logout()">Logout</a>
<a class="header__item" [routerLink]="['CreateQuestion']">New Question</a>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
directives: [ROUTER_DIRECTIVES]
})
export class AppHeader {
constructor(private nav: Nav){

constructor(private nav: Nav, public auth: FirebaseAuth){
this.auth.subscribe((data) => {
console.log('auth data', data);
});
}
openSideNav(){
this.nav.open()
}

login() {
this.auth.login()
.then(() => console.log("Success!"),
(err) => console.error(err));
}

logout() {
this.auth.logout();
}
}
Loading