xP: clean up

This commit is contained in:
Přemysl Eric Janouch 2022-09-07 14:07:14 +02:00
parent d55402234c
commit 2b13f891c9
Signed by: p
GPG Key ID: A0420B94F92B9493
1 changed files with 191 additions and 165 deletions

View File

@ -226,151 +226,8 @@ for (let i = 0; i < 24; i++) {
palette[232 + i] = `#${g}${g}${g}` palette[232 + i] = `#${g}${g}${g}`
} }
function applyColor(fg, bg, inverse) {
if (inverse)
[fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0]
let style = {}
if (fg >= 0)
style.color = palette[fg]
if (bg >= 0)
style.backgroundColor = palette[bg]
if (style)
return style
}
// ---- UI --------------------------------------------------------------------- // ---- UI ---------------------------------------------------------------------
let BufferList = {
view: vnode => {
let items = Array.from(buffers, ([name, b]) => {
let attrs = {
onclick: event =>
rpc.send({command: 'BufferActivate', bufferName: name}),
}
if (name == bufferCurrent)
attrs.class = 'active'
return m('.item', attrs, name)
})
return m('.list', {}, items)
},
}
function linkify(text, attrs, a) {
let re = new RegExp([
/https?:\/\//,
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
/[^\[\](){}<>"'\s,.:]/,
].map(r => r.source).join(''), 'g')
let end = 0, match
while ((match = re.exec(text)) !== null) {
if (end < match.index)
a.push(m('span', attrs, text.substring(end, match.index)))
a.push(m('a', {href: match[0], ...attrs}, match[0]))
end = re.lastIndex
}
if (end < text.length)
a.push(m('span', attrs, text.substring(end)))
}
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
case 'Action': content.push(m('span.mark.action', {}, '✶')); break
}
let classes = new Set()
let flip = c => {
if (classes.has(c))
classes.delete(c)
else
classes.add(c)
}
let fg = -1, bg = -1, inverse = false
line.items.forEach(item => {
switch (item.kind) {
case 'Text':
linkify(item.text, {
class: Array.from(classes.keys()).join(' '),
style: applyColor(fg, bg, inverse),
}, content)
break
case 'Reset':
classes.clear()
fg = bg = -1
inverse = false
break
case 'FgColor':
fg = item.color
break
case 'BgColor':
bg = item.color
break
case 'FlipInverse':
inverse = !inverse
break
case 'FlipBold': flip('b'); break
case 'FlipItalic': flip('i'); break
case 'FlipUnderline': flip('u'); break
case 'FlipCrossedOut': flip('s'); break
case 'FlipMonospace': flip('m'); break
}
})
return m('.content', {}, content)
},
}
let Buffer = {
oncreate: vnode => {
if (vnode.dom === undefined ||
bufferLog !== undefined || !bufferAutoscroll)
return
let el = vnode.dom.children[1]
if (el !== null)
el.scrollTop = el.scrollHeight
},
onupdate: vnode => {
Buffer.oncreate(vnode)
},
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)
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'),
bufferLog !== undefined
? m(".log", {}, bufferLog)
: m('.buffer', {}, lines),
])
},
}
let Toolbar = { let Toolbar = {
toggleAutoscroll: () => { toggleAutoscroll: () => {
bufferAutoscroll = !bufferAutoscroll bufferAutoscroll = !bufferAutoscroll
@ -401,17 +258,173 @@ let Toolbar = {
}, },
} }
function onKeyDown(event) { let BufferList = {
// TODO: And perhaps on other actions, too. activate: name => {
rpc.send({command: 'Active'}) rpc.send({command: 'BufferActivate', bufferName: name})
},
// TODO: Cancel any current autocomplete. view: vnode => {
let items = Array.from(buffers, ([name, b]) => {
let attrs = {onclick: event => BufferList.activate(name)}
if (name == bufferCurrent)
attrs.class = 'active'
return m('.item', attrs, name)
})
return m('.list', {}, items)
},
}
let textarea = event.currentTarget let Content = {
switch (event.keyCode) { applyColor: (fg, bg, inverse) => {
case 9: if (inverse)
if (textarea.selectionStart !== textarea.selectionEnd) [fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0]
let style = {}
if (fg >= 0)
style.color = palette[fg]
if (bg >= 0)
style.backgroundColor = palette[bg]
if (style)
return style
},
linkify: (text, attrs) => {
let re = new RegExp([
/https?:\/\//,
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
/[^\[\](){}<>"'\s,.:]/,
].map(r => r.source).join(''), 'g')
let a = [], end = 0, match
while ((match = re.exec(text)) !== null) {
if (end < match.index)
a.push(m('span', attrs, text.substring(end, match.index)))
a.push(m('a', {href: match[0], ...attrs}, match[0]))
end = re.lastIndex
}
if (end < text.length)
a.push(m('span', attrs, text.substring(end)))
return a
},
view: vnode => {
let line = vnode.children[0]
let mark = undefined
switch (line.rendition) {
case 'Indent': mark = m('span.mark', {}, ''); break
case 'Status': mark = m('span.mark', {}, ''); break
case 'Error': mark = m('span.mark.error', {}, '⚠'); break
case 'Join': mark = m('span.mark.join', {}, '→'); break
case 'Part': mark = m('span.mark.part', {}, '←'); break
case 'Action': mark = m('span.mark.action', {}, '✶'); break
}
let classes = new Set()
let flip = c => {
if (classes.has(c))
classes.delete(c)
else
classes.add(c)
}
let fg = -1, bg = -1, inverse = false
return m('.content', {}, [mark, line.items.flatMap(item => {
switch (item.kind) {
case 'Text':
return Content.linkify(item.text, {
class: Array.from(classes.keys()).join(' '),
style: Content.applyColor(fg, bg, inverse),
})
case 'Reset':
classes.clear()
fg = bg = -1
inverse = false
break
case 'FgColor':
fg = item.color
break
case 'BgColor':
bg = item.color
break
case 'FlipInverse':
inverse = !inverse
break
case 'FlipBold':
flip('b')
break
case 'FlipItalic':
flip('i')
break
case 'FlipUnderline':
flip('u')
break
case 'FlipCrossedOut':
flip('s')
break
case 'FlipMonospace':
flip('m')
break
}
})])
},
}
let Buffer = {
oncreate: vnode => {
if (vnode.dom !== undefined && bufferAutoscroll)
vnode.dom.scrollTop = vnode.dom.scrollHeight
},
onupdate: vnode => {
Buffer.oncreate(vnode)
},
view: vnode => {
let lines = []
let b = buffers.get(bufferCurrent)
if (b === undefined)
return return
let lastDateMark = undefined
b.lines.forEach(line => {
let date = new Date(line.when)
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', {}, lines)
},
}
let Log = {
oncreate: vnode => {
if (vnode.dom !== undefined)
vnode.dom.scrollTop = vnode.dom.scrollHeight
},
view: vnode => {
return m(".log", {}, bufferLog)
},
}
let BufferContainer = {
view: vnode => {
return m('.buffer-container', {}, [
m('.filler'),
bufferLog !== undefined ? m(Log) : m(Buffer),
])
},
}
let Input = {
complete: textarea => {
if (textarea.selectionStart !== textarea.selectionEnd)
return false
rpc.send({ rpc.send({
command: 'BufferComplete', command: 'BufferComplete',
bufferName: bufferCurrent, bufferName: bufferCurrent,
@ -426,28 +439,41 @@ function onKeyDown(event) {
textarea.setRangeText(' ', textarea.setRangeText(' ',
textarea.selectionStart, textarea.selectionEnd, 'end') textarea.selectionStart, textarea.selectionEnd, 'end')
}) })
break; return true
case 13: },
submit: textarea => {
rpc.send({ rpc.send({
command: 'BufferInput', command: 'BufferInput',
bufferName: bufferCurrent, bufferName: bufferCurrent,
text: textarea.value, text: textarea.value,
}) })
textarea.value = '' textarea.value = ''
break; return true
default: },
return
}
event.preventDefault() onKeyDown: event => {
} // TODO: And perhaps on other actions, too.
rpc.send({command: 'Active'})
// TODO: Cancel any current autocomplete.
let textarea = event.currentTarget
let handled = false
switch (event.keyCode) {
case 9:
handled = Input.complete(textarea)
break
case 13:
handled = Input.submit(textarea)
break
}
if (handled)
event.preventDefault()
},
let Input = {
view: vnode => { view: vnode => {
return m('textarea#input', { return m('textarea#input', {rows: 1, onkeydown: Input.onKeyDown})
rows: 1,
onkeydown: onKeyDown,
})
}, },
} }
@ -461,7 +487,7 @@ let Main = {
return m('.xP', {}, [ return m('.xP', {}, [
m('.title', {}, [`xP (${state})`, m(Toolbar)]), m('.title', {}, [`xP (${state})`, m(Toolbar)]),
m('.middle', {}, [m(BufferList), m(Buffer)]), m('.middle', {}, [m(BufferList), m(BufferContainer)]),
m('.status', {}, bufferCurrent), m('.status', {}, bufferCurrent),
m(Input), m(Input),
]) ])