189 lines
4.1 KiB
JavaScript
189 lines
4.1 KiB
JavaScript
// 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,
|
||
})
|
||
})
|