|
@@ -1,7 +1,11 @@
|
|
|
var Cycle = require('cyclejs');
|
|
|
var Rx = Cycle.Rx;
|
|
|
+var RxDOM = require('rx-dom').DOM;
|
|
|
var h = Cycle.h;
|
|
|
-var about = require('./templates/about');
|
|
|
+
|
|
|
+var hashDriver = require('./drivers/hashDriver');
|
|
|
+var util = require('./util');
|
|
|
+var chat = require('./services/chat');
|
|
|
|
|
|
var nav = [
|
|
|
{partial: 'about', name: 'About'},
|
|
@@ -12,6 +16,15 @@ var nav = [
|
|
|
function intent(drivers) {
|
|
|
var DOM = drivers.DOM;
|
|
|
return {
|
|
|
+ route$: drivers.hash.map(function(route) {
|
|
|
+ return route || 'about';
|
|
|
+ }),
|
|
|
+ chat$: DOM.get('#talk', 'input').map(function(ev) {
|
|
|
+ return ev.target.value;
|
|
|
+ }).startWith('').shareReplay(1),
|
|
|
+ send$: DOM.get('#talk', 'keyup').filter(function(ev) {
|
|
|
+ return ev.keyCode == 13 && ev.target.value.trim();
|
|
|
+ }).shareReplay(1),
|
|
|
disconnect$: DOM.get('#disconnect', 'click'),
|
|
|
username$: DOM.get('#username', 'input').map(function(ev) {
|
|
|
return ev.target.value;
|
|
@@ -24,28 +37,100 @@ function intent(drivers) {
|
|
|
}
|
|
|
|
|
|
function model(actions) {
|
|
|
+ var route$ = actions.route$.shareReplay(1);
|
|
|
+
|
|
|
+ var username$ = actions.login$
|
|
|
+ .withLatestFrom(actions.username$, function(submit, username) {
|
|
|
+ return username;
|
|
|
+ })
|
|
|
+ .merge(actions.disconnect$.map(function() { return null; }))
|
|
|
+ .startWith(null)
|
|
|
+ .shareReplay(1)
|
|
|
+
|
|
|
+ var room$ = route$
|
|
|
+ .map(function(url) {
|
|
|
+ if(url.startsWith('chatRoom/')) {
|
|
|
+ return url.replace('chatRoom/', '');
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .startWith(null)
|
|
|
+ .distinctUntilChanged();
|
|
|
+
|
|
|
+ var outgoing$ = util.sync(actions.send$, actions.chat$)
|
|
|
+ .map(function(msg) {
|
|
|
+ return JSON.stringify({text: msg});
|
|
|
+ })
|
|
|
+
|
|
|
+ var results = chat.connect(username$, room$, outgoing$);
|
|
|
+
|
|
|
+ var newRoute$ = results.details$
|
|
|
+ .filter(function(params) {
|
|
|
+ return params.username != null;
|
|
|
+ })
|
|
|
+ .map(function(params) {
|
|
|
+ return 'chatRoom/'+params.room;
|
|
|
+ })
|
|
|
+ .distinctUntilChanged()
|
|
|
+
|
|
|
+ var input$ = actions.chat$.merge(actions.send$.map(function() { return '' }));
|
|
|
+
|
|
|
+ var messages$ = results.ws$
|
|
|
+ .map(function(message) {
|
|
|
+ if(message.kind === 'talk') {
|
|
|
+ return message;
|
|
|
+ }
|
|
|
+ else if(message.kind == "join") {
|
|
|
+ return {
|
|
|
+ kind: 'join',
|
|
|
+ user: message.user,
|
|
|
+ message: ' has joined.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else if(message.kind == "quit") {
|
|
|
+ return {
|
|
|
+ kind: 'quit',
|
|
|
+ user: message.user,
|
|
|
+ message: ' has left.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ else return null;
|
|
|
+ })
|
|
|
+ .filter(function(item) {
|
|
|
+ return item != null;
|
|
|
+ })
|
|
|
+ .scan([], function(a, b) {
|
|
|
+ a.push(b);
|
|
|
+ return a;
|
|
|
+ })
|
|
|
+ .startWith([])
|
|
|
+
|
|
|
return {
|
|
|
- username$: actions.login$
|
|
|
- .withLatestFrom(actions.username$, function(submit, username) {
|
|
|
- return username;
|
|
|
- })
|
|
|
- .merge(actions.disconnect$.map(function() { return null; }))
|
|
|
- .startWith(null)
|
|
|
- ,
|
|
|
- route$: Rx.Observable.fromEvent(window, 'hashchange')
|
|
|
- .map(function(hashEvent) { return hashEvent.target.location.hash.replace('#', '') })
|
|
|
- .startWith(window.location.hash.replace('#', '') || 'about')
|
|
|
- }
|
|
|
+ hash: newRoute$,
|
|
|
+ DOM: {
|
|
|
+ username$: username$,
|
|
|
+ room$: room$,
|
|
|
+ details$: results.details$,
|
|
|
+ route$: route$,
|
|
|
+ status$: results.status$,
|
|
|
+ error$: results.error$,
|
|
|
+ chat$: input$,
|
|
|
+ messages$: messages$
|
|
|
+ }
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
-function renderLogin(username) {
|
|
|
+function renderLogin(props) {
|
|
|
+ var isConnected = props.status === 'connected';
|
|
|
var content;
|
|
|
- if(!username) {
|
|
|
+ if(!isConnected) {
|
|
|
content = [ h('a.navbar-link', {href: '#chatRoom'}, ['Play!']) ];
|
|
|
}
|
|
|
- if(username) {
|
|
|
+ else {
|
|
|
content = [
|
|
|
- "Logged in as "+username+" — ",
|
|
|
+ "Logged in as "+props.username+" — ",
|
|
|
h('a.navbar-link#disconnect', ['Disconnect'])
|
|
|
];
|
|
|
}
|
|
@@ -54,7 +139,7 @@ function renderLogin(username) {
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
-function renderNav(route, username) {
|
|
|
+function renderNav(props) {
|
|
|
return (
|
|
|
h('nav', {className: 'navbar navbar-inverse navbar-static-top', role: 'navigation'}, [
|
|
|
h('div.container', [
|
|
@@ -62,15 +147,19 @@ function renderNav(route, username) {
|
|
|
h('span.brand.navbar-brand', ["Game 'n Chat"])
|
|
|
]),
|
|
|
h('ul', {className: 'nav navbar-nav collapse navbar-collapse'}, nav.map(function(item) {
|
|
|
+ var link = '#'+item.partial;
|
|
|
+ if(item.partial === 'chatRoom' && props.status === 'connected' && props.details.room) {
|
|
|
+ link += '/' + props.details.room;
|
|
|
+ }
|
|
|
var className = '';
|
|
|
- if(item.partial == route) {
|
|
|
+ if(item.partial == props.route) {
|
|
|
className = 'active';
|
|
|
}
|
|
|
return h('li', {className: className}, [
|
|
|
- h('a', {href: '#'+item.partial}, [ item.name ])
|
|
|
+ h('a', {href: link}, [ item.name ])
|
|
|
]);
|
|
|
})),
|
|
|
- renderLogin(username)
|
|
|
+ renderLogin(props)
|
|
|
]),
|
|
|
])
|
|
|
)
|
|
@@ -84,8 +173,9 @@ function renderFooter() {
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
-function renderContainer(route) {
|
|
|
- var content = require('./templates/'+route)();
|
|
|
+function renderContainer(props) {
|
|
|
+ var route = props.route.replace(/\/.*/, '');
|
|
|
+ var content = require('./templates/'+route)(props);
|
|
|
return h('div.container', [
|
|
|
h('div.content', [content]),
|
|
|
renderFooter()
|
|
@@ -93,22 +183,23 @@ function renderContainer(route) {
|
|
|
}
|
|
|
|
|
|
function view(model) {
|
|
|
- return Rx.Observable.combineLatest(
|
|
|
- model.route$,
|
|
|
- model.username$,
|
|
|
- function(route, username) {
|
|
|
- return h('div', [
|
|
|
- renderNav(route, username),
|
|
|
- renderContainer(route)
|
|
|
- ]);
|
|
|
- }
|
|
|
- );
|
|
|
+ return {
|
|
|
+ hash: model.hash,
|
|
|
+ DOM:
|
|
|
+ util.asObject(model.DOM).map(function(model) {
|
|
|
+ return h('div', [
|
|
|
+ renderNav(model),
|
|
|
+ renderContainer(model)
|
|
|
+ ]);
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function main(drivers) {
|
|
|
- return { DOM: view(model(intent(drivers))) };
|
|
|
+ return view(model(intent(drivers)));
|
|
|
}
|
|
|
|
|
|
Cycle.run(main, {
|
|
|
- DOM: Cycle.makeDOMDriver('body')
|
|
|
+ DOM: Cycle.makeDOMDriver('body'),
|
|
|
+ hash: hashDriver
|
|
|
});
|