index.js 5.0 KB

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