index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. var Cycle = require('cyclejs');
  2. var Rx = Cycle.Rx;
  3. var RxDOM = require('rx-dom').DOM;
  4. var h = Cycle.h;
  5. var hashDriver = require('./drivers/hashDriver');
  6. var util = require('./util');
  7. var chat = require('./services/chat');
  8. var nav = [
  9. {partial: 'about', name: 'About'},
  10. {partial: 'chatRoom', name: 'Chat'},
  11. {partial: 'contribute', name: 'Contribute'}
  12. ];
  13. function intent(drivers) {
  14. var DOM = drivers.DOM;
  15. return {
  16. route$: drivers.hash.map(function(route) {
  17. return route || 'about';
  18. }),
  19. chat$: DOM.get('#talk', 'input').map(function(ev) {
  20. return ev.target.value;
  21. }).startWith('').shareReplay(1),
  22. send$: DOM.get('#talk', 'keyup').filter(function(ev) {
  23. return ev.keyCode == 13 && ev.target.value.trim();
  24. }).shareReplay(1),
  25. disconnect$: DOM.get('#disconnect', 'click'),
  26. username$: DOM.get('#username', 'input').map(function(ev) {
  27. return ev.target.value;
  28. }),
  29. login$: DOM.get('#login-form', 'submit').map(function(ev) {
  30. ev.preventDefault();
  31. return true;
  32. })
  33. };
  34. }
  35. function model(actions) {
  36. var route$ = actions.route$.shareReplay(1);
  37. var username$ = actions.login$
  38. .withLatestFrom(actions.username$, function(submit, username) {
  39. return username;
  40. })
  41. .merge(actions.disconnect$.map(function() { return null; }))
  42. .startWith(null)
  43. .shareReplay(1)
  44. var room$ = route$
  45. .map(function(url) {
  46. if(url.startsWith('chatRoom/')) {
  47. return url.replace('chatRoom/', '');
  48. }
  49. else {
  50. return null;
  51. }
  52. })
  53. .startWith(null)
  54. .distinctUntilChanged();
  55. var outgoing$ = util.sync(actions.send$, actions.chat$)
  56. .map(function(msg) {
  57. return JSON.stringify({text: msg});
  58. })
  59. var results = chat.connect(username$, room$, outgoing$);
  60. var newRoute$ = results.details$
  61. .filter(function(params) {
  62. return params.username != null;
  63. })
  64. .map(function(params) {
  65. return 'chatRoom/'+params.room;
  66. })
  67. .distinctUntilChanged()
  68. var input$ = actions.chat$.merge(actions.send$.map(function() { return '' }));
  69. var messages$ = results.ws$
  70. .map(function(message) {
  71. if(message.kind === 'talk') {
  72. return message;
  73. }
  74. else if(message.kind == "join") {
  75. return {
  76. kind: 'join',
  77. user: message.user,
  78. message: ' has joined.'
  79. };
  80. }
  81. else if(message.kind == "quit") {
  82. return {
  83. kind: 'quit',
  84. user: message.user,
  85. message: ' has left.'
  86. };
  87. }
  88. else return null;
  89. })
  90. .filter(function(item) {
  91. return item != null;
  92. })
  93. .scan([], function(a, b) {
  94. a.push(b);
  95. return a;
  96. })
  97. .startWith([])
  98. return {
  99. hash: newRoute$,
  100. DOM: {
  101. username$: username$,
  102. room$: room$,
  103. details$: results.details$,
  104. route$: route$,
  105. status$: results.status$,
  106. error$: results.error$,
  107. chat$: input$,
  108. messages$: messages$
  109. }
  110. };
  111. }
  112. function renderLogin(props) {
  113. var isConnected = props.status === 'connected';
  114. var content;
  115. if(!isConnected) {
  116. content = [ h('a.navbar-link', {href: '#chatRoom'}, ['Play!']) ];
  117. }
  118. else {
  119. content = [
  120. "Logged in as "+props.username+" — ",
  121. h('a.navbar-link#disconnect', ['Disconnect'])
  122. ];
  123. }
  124. return h('div', {className: 'navbar-right collapse navbar-collapse'}, [
  125. h('p.navbar-text', content)
  126. ]);
  127. }
  128. function renderNav(props) {
  129. return (
  130. h('nav', {className: 'navbar navbar-inverse navbar-static-top', role: 'navigation'}, [
  131. h('div.container', [
  132. h('div.navbar-header', [
  133. h('span.brand.navbar-brand', ["Game 'n Chat"])
  134. ]),
  135. h('ul', {className: 'nav navbar-nav collapse navbar-collapse'}, nav.map(function(item) {
  136. var link = '#'+item.partial;
  137. if(item.partial === 'chatRoom' && props.status === 'connected' && props.details.room) {
  138. link += '/' + props.details.room;
  139. }
  140. var className = '';
  141. if(item.partial == props.route) {
  142. className = 'active';
  143. }
  144. return h('li', {className: className}, [
  145. h('a', {href: link}, [ item.name ])
  146. ]);
  147. })),
  148. renderLogin(props)
  149. ]),
  150. ])
  151. )
  152. }
  153. function renderFooter() {
  154. return h('footer', [
  155. h('p', [
  156. h('a', {href: 'http://twitter.com/pleasantprog', target: '_blank'}, ['@pleasantprog'])
  157. ])
  158. ]);
  159. }
  160. function renderContainer(props) {
  161. var route = props.route.replace(/\/.*/, '');
  162. var content = require('./templates/'+route)(props);
  163. return h('div.container', [
  164. h('div.content', [content]),
  165. renderFooter()
  166. ]);
  167. }
  168. function view(model) {
  169. return {
  170. hash: model.hash,
  171. DOM:
  172. util.asObject(model.DOM).map(function(model) {
  173. return h('div', [
  174. renderNav(model),
  175. renderContainer(model)
  176. ]);
  177. })
  178. }
  179. }
  180. function main(drivers) {
  181. return view(model(intent(drivers)));
  182. }
  183. Cycle.run(main, {
  184. DOM: Cycle.makeDOMDriver('body'),
  185. hash: hashDriver
  186. });