Add cursor and theme commands

Also I didn't have to re-create that horrible demo screen Pork made by
manually typing in esc sequences
cpsdqs/unified-input
cpsdqs 7 years ago
parent 0875182d38
commit 720a9ecaa1
Signed by untrusted user: cpsdqs
GPG Key ID: 3F59586BB7448DD1
  1. 202
      js/demo.js

@ -58,6 +58,11 @@ class ANSIParser {
if (type === 48) this.handler('set-color-bg', color) if (type === 48) this.handler('set-color-bg', color)
} }
} }
} else if (type === 'h' || type === 'l') {
if (content === '?25') {
if (type === 'h') this.handler('show-cursor')
else if (type === 'l') this.handler('hide-cursor')
}
} }
} }
} }
@ -105,8 +110,9 @@ class ScrollingTerminal {
} }
reset () { reset () {
this.style = TERM_DEFAULT_STYLE this.style = TERM_DEFAULT_STYLE
this.cursor = { x: 0, y: 0, style: 1 } this.cursor = { x: 0, y: 0, style: 1, visible: true }
this.trackMouse = false this.trackMouse = false
this.theme = 0
this.parser.reset() this.parser.reset()
this.clear() this.clear()
} }
@ -218,6 +224,10 @@ class ScrollingTerminal {
this.style = (this.style & 0xFFFF00) | args[0] this.style = (this.style & 0xFFFF00) | args[0]
} else if (action === 'set-color-bg') { } else if (action === 'set-color-bg') {
this.style = (this.style & 0xFF00FF) | (args[0] << 8) this.style = (this.style & 0xFF00FF) | (args[0] << 8)
} else if (action === 'hide-cursor') {
this.cursor.visible = false
} else if (action === 'show-cursor') {
this.cursor.visible = true
} }
} }
write (text) { write (text) {
@ -229,7 +239,7 @@ class ScrollingTerminal {
serialized += encode2B(this.height) + encode2B(this.width) serialized += encode2B(this.height) + encode2B(this.width)
serialized += encode2B(this.cursor.y) + encode2B(this.cursor.x) serialized += encode2B(this.cursor.y) + encode2B(this.cursor.x)
let attributes = 1 // cursor always visible let attributes = +this.cursor.visible
attributes |= (3 << 5) * +this.trackMouse // track mouse controls both attributes |= (3 << 5) * +this.trackMouse // track mouse controls both
attributes |= 3 << 7 // buttons/links always visible attributes |= 3 << 7 // buttons/links always visible
attributes |= (this.cursor.style << 9) attributes |= (this.cursor.style << 9)
@ -259,7 +269,7 @@ class ScrollingTerminal {
scheduleLoad () { scheduleLoad () {
clearInterval(this._scheduledLoad) clearInterval(this._scheduledLoad)
if (this._lastLoad < Date.now() - TERM_MIN_DRAW_DELAY) { if (this._lastLoad < Date.now() - TERM_MIN_DRAW_DELAY) {
this.termScreen.load(this.serialize()) this.termScreen.load(this.serialize(), this.theme)
} else { } else {
this._scheduledLoad = setTimeout(() => { this._scheduledLoad = setTimeout(() => {
this.termScreen.load(this.serialize()) this.termScreen.load(this.serialize())
@ -381,7 +391,7 @@ let demoshIndex = {
'local-echo': class LocalEcho extends Process { 'local-echo': class LocalEcho extends Process {
run (...args) { run (...args) {
if (!args.includes('--suppress-note')) { if (!args.includes('--suppress-note')) {
this.emit('write', 'Note: not all terminal features are supported or bug-free in this demo\x1b[0m\r\n') this.emit('write', '\x1b[38;5;239mNote: not all terminal features are supported or and may not work as expected in this demo\x1b[0m\r\n')
} }
} }
write (data) { write (data) {
@ -390,74 +400,92 @@ let demoshIndex = {
}, },
'info': class Info extends Process { 'info': class Info extends Process {
run (...args) { run (...args) {
let fast = args.includes('--fast')
this.showSplash().then(() => {
this.printText(fast)
})
}
showSplash () {
let splash = `
-#####- -###*..#####- ######-
-#* -#- .## .##. *#-
-##### .-###*..#####- *#- -*##*- #*-#--#**#-*##-
-#* -#-.##. *#- *##-#* ##. -#* *# .#*
-#####--####- .##. *#- -*###- ##. -#* *# .#*
`.split('\n').filter(line => line.trim())
let levels = {
' ': -231,
'.': 4,
'-': 8,
'*': 17,
'#': 24
}
for (let i in splash) {
if (splash[i].length < 79) splash[i] += ' '.repeat(79 - splash[i].length)
}
this.emit('write', '\r\n'.repeat(splash.length + 1))
this.emit('write', '\x1b[A'.repeat(splash.length))
this.emit('write', '\x1b[?25l')
let cursorX = 0
let cursorY = 0
let moveTo = (x, y) => {
let moveX = x - cursorX
let moveY = y - cursorY
this.emit('write', `\x1b[${Math.abs(moveX)}${moveX > 0 ? 'C' : 'D'}`)
this.emit('write', `\x1b[${Math.abs(moveY)}${moveY > 0 ? 'B' : 'A'}`)
cursorX = x
cursorY = y
}
let drawCell = (x, y) => {
moveTo(x, y)
this.emit('write', `\x1b[48;5;${231 + levels[splash[y][x]]}m \b`)
}
return new Promise((resolve, reject) => {
const self = this
let x = 14
let cycles = 0
let loop = function () {
for (let y = 0; y < splash.length; y++) {
let dx = x - y
if (dx > 0) drawCell(dx, y)
}
if (++x < 79) {
if (++cycles >= 3) {
setTimeout(loop, 20)
cycles = 0
} else loop()
} else {
moveTo(0, splash.length)
self.emit('write', '\x1b[m\x1b[?25h')
resolve()
}
}
loop()
})
}
printText (fast = false) {
// lots of printing // lots of printing
let parts = [] let parts = [
parts.push('\r\n') '',
parts.push('┌ESPTerm─Demo──') ' ESPTerm is a VT100-like terminal emulator running on the ESP8266 WiFi chip.',
parts.push('\x1b[31m31\x1b[32m32\x1b[33m33\x1b[34m34\x1b[35m35\x1b[36m36\x1b[37m37') '',
parts.push('\x1b[90m90\x1b[91m91\x1b[92m92\x1b[93m93\x1b[94m94\x1b[95m95\x1b[96m96\x1b[97m97') ' \x1b[93mThis is an online demo of the web user interface.\x1b[m',
parts.push('\x1b[0m─────────────┐\r\n') '',
parts.push('│') ' Type \x1b[92mls\x1b[m to list available commands.',
for (let i = 0; i < 57; i++) parts.push(' ') ' Use the \x1b[94mlinks\x1b[m below this screen for a demo of the options and more info.',
parts.push('│') ''
parts.push(' │││││││││\r\n') ]
parts.push('│')
parts.push('\x1b[1mBold \x1b[m\x1b[2mFaint \x1b[m\x1b[3mItalic \x1b[m\x1b[4mUnderline\x1b[0m \x1b[m\x1b[5mBlink')
parts.push(' \x1b[m\x1b[7mInverse\x1b[m \x1b[9mStrike\x1b[m \x1b[20mFraktur \x1b[m')
parts.push('│')
parts.push(' ──\x1b[100m \x1b[m──\r\n')
parts.push('│')
for (let i = 0; i < 57; i++) parts.push(' ')
parts.push('│')
parts.push(' ──\x1b[100;30m ESP8266 \x1b[m──\r\n')
parts.push('└')
for (let i = 0; i < 57; i++) parts.push('─')
parts.push('┤')
parts.push(' ──\x1b[100m \x1b[m──\r\n')
for (let i = 0; i < 58; i++) parts.push(' ')
parts.push('│')
parts.push(' ──\x1b[100;30m (@)#### \x1b[m──\r\n')
parts.push(' \x1b[44;96m This is a demo of the ESPTerm Web Interface \x1b[m ')
parts.push('│')
parts.push(' ──\x1b[100m \x1b[m──\r\n')
parts.push(' \x1b[44;96m \x1b[m ')
parts.push('│')
parts.push(' │││││││││\r\n')
parts.push(' \x1b[44;96m Try the links beneath this screen to browse the menu. \x1b[m ♦\r\n')
parts.push(' \x1b[44;96m \x1b[m\r\n')
parts.push(' \x1b[44;96m <°)))>< ESPTerm fully supports UTF-8 お は よ ー ><(((°> \x1b[m\r\n')
parts.push(' \x1b[44;96m \x1b[m\r\n')
parts.push('\r\n')
parts.push(' \x1b[92mOther interesting features:\x1b[m ↓\r\n\n')
parts.push(' \x1b[32m- Almost full VT100 emulation \x1b[35m() ()')
parts.push('\x1b[0m Funguje tu čeština!\r\n')
parts.push(' \x1b[34m- Xterm-like mouse tracking \x1b[37m==\x1b[100m°.°\x1b[40m==')
parts.push(' \x1b[35m<---,\r\n')
parts.push(" \x1b[33m- File upload utility \x1b[0m'' '' \x1b[35mmouse\r\n")
parts.push(' \x1b[31m- User-friendly config interface\r\n')
parts.push(' \x1b[95m- Advanced WiFi & network settings')
for (let i = 0; i < 17; i++) parts.push(' ')
parts.push('\x1b[93mTry ESPTerm today!\r\n')
parts.push(' \x1b[37m- Built-in help page')
for (let i = 0; i < 26; i++) parts.push(' ')
parts.push('\x1b[36m--> \x1b[93mPre-built binaries are\r\n')
for (let i = 0; i < 30; i++) parts.push(' ')
parts.push('\x1b[36mlink on the About page \x1b[93mavailable on GitHub!\r\n')
let chars = parts.join('') if (fast) {
if (args.includes('--fast')) { this.emit('write', parts.join('\r\n') + '\r\n')
this.emit('write', chars)
this.destroy() this.destroy()
} else { } else {
const self = this const self = this
let loop = function () { let loop = function () {
while (true) { self.emit('write', parts.shift() + '\r\n')
let character = chars[0] if (parts.length) setTimeout(loop, 17)
chars = chars.substr(1)
self.emit('write', character)
if (character === '\n') break
}
if (chars) setTimeout(loop, 17)
else self.destroy() else self.destroy()
} }
loop() loop()
@ -506,6 +534,40 @@ let demoshIndex = {
this.destroy() this.destroy()
} }
}, },
theme: class SetTheme extends Process {
constructor (shell) {
super()
this.shell = shell
}
run (...args) {
let theme = args[0] | 0
if (!args.length || !Number.isFinite(theme) || theme < 0 || theme > 5) {
this.emit('write', '\x1b[31mUsage: theme [0–5]\r\n')
this.destroy()
return
}
this.shell.terminal.theme = theme
// HACK: reset drawn screen to prevent only partly redrawn screen
this.shell.terminal.termScreen.drawnScreenFG = []
this.emit('write', '')
this.destroy()
}
},
cursor: class SetCursor extends Process {
run (...args) {
let steady = args.includes('--steady')
if (args.includes('block')) {
this.emit('write', `\x1b[${0 + 2 * steady} q`)
} else if (args.includes('line')) {
this.emit('write', `\x1b[${3 + steady} q`)
} else if (args.includes('bar') || args.includes('beam')) {
this.emit('write', `\x1b[${5 + steady} q`)
} else {
this.emit('write', '\x1b[31mUsage: cursor [block|line|bar] [--steady]\r\n')
}
this.destroy()
}
},
pwd: '/this/is/a/demo\r\n', pwd: '/this/is/a/demo\r\n',
cd: '\x1b[38;5;239mNo directories to change to\r\n', cd: '\x1b[38;5;239mNo directories to change to\r\n',
whoami: `${window.navigator.userAgent}\r\n`, whoami: `${window.navigator.userAgent}\r\n`,
@ -516,7 +578,8 @@ let demoshIndex = {
cp: '\x1b[38;5;239mNothing to copy because this is a demo.\r\n', cp: '\x1b[38;5;239mNothing to copy because this is a demo.\r\n',
mv: '\x1b[38;5;239mNothing to move because this is a demo.\r\n', mv: '\x1b[38;5;239mNothing to move because this is a demo.\r\n',
ln: '\x1b[38;5;239mNothing to link because this is a demo.\r\n', ln: '\x1b[38;5;239mNothing to link because this is a demo.\r\n',
touch: '\x1b[38;5;239mNothing to touch\r\n' touch: '\x1b[38;5;239mNothing to touch\r\n',
exit: '\x1b[38;5;239mNowhere to go\r\n'
} }
class DemoShell { class DemoShell {
@ -529,7 +592,7 @@ class DemoShell {
this.child = null this.child = null
this.index = demoshIndex this.index = demoshIndex
if (printInfo) this.run('info --fast') if (printInfo) this.run('info')
else this.prompt() else this.prompt()
} }
write (text) { write (text) {
@ -578,6 +641,7 @@ class DemoShell {
} }
} }
parse (input) { parse (input) {
if (input === 'help') input = 'info'
// TODO: basic chaining (i.e. semicolon) // TODO: basic chaining (i.e. semicolon)
this.run(input) this.run(input)
} }
@ -608,7 +672,7 @@ class DemoShell {
spawn (name, args = []) { spawn (name, args = []) {
let Process = this.index[name] let Process = this.index[name]
if (Process instanceof Function) { if (Process instanceof Function) {
this.child = new Process(...args) this.child = new Process(this)
let write = data => this.terminal.write(data) let write = data => this.terminal.write(data)
this.child.on('write', write) this.child.on('write', write)
this.child.on('exit', code => { this.child.on('exit', code => {

Loading…
Cancel
Save