xP: clean up
This commit is contained in:
parent
d55402234c
commit
2b13f891c9
354
xP/public/xP.js
354
xP/public/xP.js
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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()
|
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),
|
||||||
])
|
])
|
||||||
|
|
Loading…
Reference in New Issue