You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 line
3.1KB

  1. const State = {
  2. websocket: null,
  3. posts: [],
  4. users: [],
  5. }
  6. /*
  7. *
  8. * GUI
  9. *
  10. */
  11. const autoFocus = (vnode) => {
  12. vnode.dom.focus()
  13. }
  14. const scrollIntoView = (vnode) => {
  15. vnode.dom.scrollIntoView()
  16. }
  17. const prettyTime = (ts) => {
  18. return ts.slice(11, 19)
  19. }
  20. const Login = {
  21. sendLogin: (e) => {
  22. e.preventDefault()
  23. const username = e.target.username.value
  24. localStorage.username = username
  25. connect(username)
  26. },
  27. sendLogout: (e) => {
  28. State.websocket.send(JSON.stringify({action: 'logout'}))
  29. State.posts = []
  30. },
  31. view() {
  32. return m('.login',
  33. m('form', {onsubmit: Login.sendLogin},
  34. m('input', {oncreate: autoFocus, name: 'username', autocomplete: 'off', value: localStorage.username}),
  35. m('button', 'Login'),
  36. ),
  37. State.kind === 'error' && m('.error', State.info),
  38. )
  39. },
  40. }
  41. const Chat = {
  42. sendPost: (e) => {
  43. e.preventDefault()
  44. const field = e.target.text
  45. State.websocket.send(JSON.stringify({action: 'post', text: field.value}))
  46. field.value = ''
  47. },
  48. view() {
  49. return m('.chat',
  50. m('.posts',
  51. State.posts.map(post => m('.post', {oncreate: scrollIntoView},
  52. m('.ts', prettyTime(post.ts)),
  53. m('.source', post.source),
  54. m('.text', post.text),
  55. )),
  56. ),
  57. m('form.actions', {onsubmit: Chat.sendPost},
  58. m('input', {oncreate: autoFocus, name: 'text', autocomplete: 'off'}),
  59. m('button', 'Send'),
  60. ),
  61. m('.users',
  62. m('button', {onclick: Login.sendLogout}, 'Logout'),
  63. m('ul.user-list', State.users.map(username => m('li', username)))
  64. ),
  65. )
  66. },
  67. }
  68. const Main = {
  69. view() {
  70. const connected = State.websocket && State.websocket.readyState === 1
  71. return connected ? m(Chat) : m(Login)
  72. },
  73. }
  74. m.mount(document.body, Main)
  75. /*
  76. *
  77. * WEBSOCKETS
  78. *
  79. */
  80. const connect = (username) => {
  81. const wsUrl = location.href.replace('http', 'ws')
  82. State.websocket = new WebSocket(wsUrl)
  83. State.websocket.onopen = (e) => {
  84. State.websocket.send(JSON.stringify({action: 'login', username}))
  85. }
  86. State.websocket.onmessage = (e) => {
  87. const message = JSON.parse(e.data)
  88. if(message.kind === 'post') {
  89. State.posts.push(message)
  90. }
  91. else if(message.kind === 'update') {
  92. Object.assign(State, message)
  93. if(message.info) {
  94. State.posts.push({ts: message.ts, source: '~', text: message.info})
  95. }
  96. }
  97. else if(message.kind === 'error') {
  98. Object.assign(State, message)
  99. Login.sendLogout()
  100. }
  101. else {
  102. console.log(message)
  103. }
  104. m.redraw()
  105. }
  106. State.websocket.onclose = (e) => {
  107. if(!e.wasClean) {
  108. setTimeout(connect, 1000, username)
  109. }
  110. m.redraw()
  111. }
  112. }
  113. if(localStorage.username) {
  114. connect(localStorage.username)
  115. }