// TODO: Probably reset state on disconnect, and indicate to user. let socket = new WebSocket(proxy) let commandSeq = 0 function send(command) { socket.send(JSON.stringify({commandSeq: ++commandSeq, data: command})) } socket.onopen = function(event) { send({command: 'Hello', version: 1}) } let buffers = new Map() let bufferCurrent = undefined socket.onmessage = function(event) { console.log(event.data) let e = JSON.parse(event.data).data switch (e.event) { case 'BufferUpdate': { let b = buffers.get(e.bufferName) if (b === undefined) { b = {lines: []} buffers.set(e.bufferName, b) } // TODO: Update any buffer properties. break } case 'BufferRename': buffers.set(e.new, buffers.get(e.bufferName)) buffers.delete(e.bufferName) break case 'BufferRemove': buffers.delete(e.bufferName) break case 'BufferActivate': bufferCurrent = e.bufferName // TODO: Somehow scroll to the end of it immediately. // TODO: Focus the textarea. break case 'BufferLine': { let b = buffers.get(e.bufferName) if (b !== undefined) b.lines.push({when: e.when, rendition: e.rendition, items: e.items}) break } case 'BufferClear': { let b = buffers.get(e.bufferName) if (b !== undefined) b.lines.length = 0 break } } m.redraw() } let BufferList = { view: vnode => { let items = [] buffers.forEach((b, name) => { let attrs = { onclick: e => { send({command: 'BufferActivate', bufferName: name}) }, } if (name == bufferCurrent) attrs.class = 'active' items.push(m('.item', attrs, name)) }) return m('.list', {}, items) }, } let Content = { view: vnode => { let line = vnode.children[0] let content = [] switch (line.rendition) { case 'Indent': content.push(m('span.mark', {}, '')); break case 'Status': content.push(m('span.mark', {}, '–')); break case 'Error': content.push(m('span.mark.error', {}, '⚠')); break case 'Join': content.push(m('span.mark.join', {}, '→')); break case 'Part': content.push(m('span.mark.part', {}, '←')); break } let classes = new Set() let flip = c => { if (classes.has(c)) classes.delete(c) else classes.add(c) } line.items.forEach(item => { // TODO: Colours. switch (item.kind) { case 'Text': // TODO: Detect and transform links. content.push(m('span', { class: Array.from(classes.keys()).join(' '), }, item.text)) break case 'Reset': classes.clear() break case 'FlipBold': flip('b'); break case 'FlipItalic': flip('i'); break case 'FlipUnderline': flip('u'); break case 'FlipInverse': flip('i'); break case 'FlipCrossedOut': flip('s'); break case 'FlipMonospace': flip('m'); break } }) return m('.content', {}, content) }, } let Buffer = { view: vnode => { let lines = [] let b = buffers.get(bufferCurrent) if (b === undefined) return let lastDateMark = undefined b.lines.forEach(line => { let date = new Date(line.when * 1000) let dateMark = date.toLocaleDateString() if (dateMark !== lastDateMark) { lines.push(m('.date', {}, dateMark)) lastDateMark = dateMark } lines.push(m('.time', {}, date.toLocaleTimeString())) lines.push(m(Content, {}, line)) }) return m('.buffer-container', {}, [ m('.filler'), m('.buffer', {}, lines), ]) }, } // TODO: This should be remembered across buffer switches, // and we'll probably have to intercept /all/ key presses. let Input = { view: vnode => { return m('textarea', { rows: 1, onkeydown: e => { // TODO: And perhaps on other actions, too. send({command: 'Active'}) if (e.keyCode !== 13) return send({ command: 'BufferInput', bufferName: bufferCurrent, text: e.currentTarget.value, }) e.preventDefault() e.currentTarget.value = '' }, }) }, } let Main = { view: vnode => { return m('.xP', {}, [ m('.title', {}, "xP"), m('.middle', {}, [m(BufferList), m(Buffer)]), m('.status', {}, bufferCurrent), m(Input), ]) }, } // TODO: Buffer names should work as routes. window.addEventListener('load', () => { m.route(document.body, '/', { '/': Main, }) })