Make Term Conn an EventEmitter, add status screen

cpsdqs/unified-input
cpsdqs 7 years ago
parent b33695e543
commit 92c4c2ff98
Signed by untrusted user: cpsdqs
GPG Key ID: 3F59586BB7448DD1
  1. 2
      js/term.js
  2. 125
      js/term_conn.js
  3. 79
      js/term_screen.js

@ -1,7 +1,7 @@
/** Init the terminal sub-module - called from HTML */ /** Init the terminal sub-module - called from HTML */
window.termInit = function ({ labels, theme, allFn }) { window.termInit = function ({ labels, theme, allFn }) {
const screen = new TermScreen() const screen = new TermScreen()
const conn = Conn(screen) const conn = new Conn(screen)
const input = Input(conn) const input = Input(conn)
const termUpload = TermUpl(conn, input, screen) const termUpload = TermUpl(conn, input, screen)
screen.input = input screen.input = input

@ -1,31 +1,38 @@
/** Handle connections */ /** Handle connections */
window.Conn = function (screen) { window.Conn = class TermConnection extends EventEmitter {
let ws constructor (screen) {
let heartbeatTout super()
let pingIv
let xoff = false this.screen = screen
let autoXoffTout this.ws = null
let reconTout this.heartbeatTimeout = null
this.pingInterval = null
let pageShown = false this.xoff = false
this.autoXoffTimeout = null
this.reconTimeout = null
this.pageShown = false
}
function onOpen (evt) { onWSOpen (evt) {
console.log('CONNECTED') console.log('CONNECTED')
heartbeat() this.heartbeat()
doSend('i') this.send('i')
this.emit('open')
} }
function onClose (evt) { onWSClose (evt) {
console.warn('SOCKET CLOSED, code ' + evt.code + '. Reconnecting...') console.warn('SOCKET CLOSED, code ' + evt.code + '. Reconnecting...')
clearTimeout(reconTout) clearTimeout(this.reconTimeout)
reconTout = setTimeout(function () { this.reconTimeout = setTimeout(() => this.init(), 2000)
init()
}, 2000)
// this happens when the buffer gets fucked up via invalid unicode. // this happens when the buffer gets fucked up via invalid unicode.
// we basically use polling instead of socket then // we basically use polling instead of socket then
this.emit('close', evt.code)
} }
function onMessage (evt) { onWSMessage (evt) {
try { try {
// . = heartbeat // . = heartbeat
switch (evt.data.charAt(0)) { switch (evt.data.charAt(0)) {
@ -35,66 +42,66 @@ window.Conn = function (screen) {
case '-': case '-':
// console.log('xoff'); // console.log('xoff');
xoff = true this.xoff = true
autoXoffTout = setTimeout(function () { this.autoXoffTimeout = setTimeout(() => {
xoff = false this.xoff = false
}, 250) }, 250)
break break
case '+': case '+':
// console.log('xon'); // console.log('xon');
xoff = false this.xoff = false
clearTimeout(autoXoffTout) clearTimeout(this.autoXoffTimeout)
break break
default: default:
screen.load(evt.data) this.screen.load(evt.data)
if (!pageShown) { if (!this.pageShown) {
showPage() showPage()
pageShown = true this.pageShown = true
} }
break break
} }
heartbeat() this.heartbeat()
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
} }
function canSend () { canSend () {
return !xoff return !this.xoff
} }
function doSend (message) { send (message) {
if (_demo) { if (window._demo) {
if (typeof demoInterface !== 'undefined') { if (typeof window.demoInterface !== 'undefined') {
demoInterface.input(message) demoInterface.input(message)
} else { } else {
console.log(`TX: ${JSON.stringify(message)}`) console.log(`TX: ${JSON.stringify(message)}`)
} }
return true // Simulate success return true // Simulate success
} }
if (xoff) { if (this.xoff) {
// TODO queue // TODO queue
console.log("Can't send, flood control.") console.log("Can't send, flood control.")
return false return false
} }
if (!ws) return false // for dry testing if (!this.ws) return false // for dry testing
if (ws.readyState !== 1) { if (this.ws.readyState !== 1) {
console.error('Socket not ready') console.error('Socket not ready')
return false return false
} }
if (typeof message != 'string') { if (typeof message != 'string') {
message = JSON.stringify(message) message = JSON.stringify(message)
} }
ws.send(message) this.ws.send(message)
return true return true
} }
function init () { init () {
if (window._demo) { if (window._demo) {
if (typeof demoInterface === 'undefined') { if (typeof window.demoInterface === 'undefined') {
alert('Demoing non-demo demo!') // this will catch mistakes when deploying to the website alert('Demoing non-demo demo!') // this will catch mistakes when deploying to the website
} else { } else {
demoInterface.init(screen) demoInterface.init(screen)
@ -103,42 +110,40 @@ window.Conn = function (screen) {
return return
} }
clearTimeout(reconTout) clearTimeout(this.reconTimeout)
clearTimeout(heartbeatTout) clearTimeout(this.heartbeatTimeout)
ws = new WebSocket('ws://' + _root + '/term/update.ws') this.ws = new WebSocket('ws://' + _root + '/term/update.ws')
ws.onopen = onOpen this.ws.addEventListener('open', (...args) => this.onWSOpen(...args))
ws.onclose = onClose this.ws.addEventListener('close', (...args) => this.onWSClose(...args))
ws.onmessage = onMessage this.ws.addEventListener('message', (...args) => this.onWSMessage(...args))
console.log('Opening socket.') console.log('Opening socket.')
heartbeat() this.heartbeat()
this.emit('connect')
} }
function heartbeat () { heartbeat () {
clearTimeout(heartbeatTout) clearTimeout(this.heartbeatTimeout)
heartbeatTout = setTimeout(heartbeatFail, 2000) this.heartbeatTimeout = setTimeout(() => this.onHeartbeatFail(), 2000)
} }
function heartbeatFail () { onHeartbeatFail () {
console.error('Heartbeat lost, probing server...') console.error('Heartbeat lost, probing server...')
pingIv = setInterval(function () { clearInterval(this.pingInterval)
this.pingInterval = setInterval(() => {
console.log('> ping') console.log('> ping')
$.get('http://' + _root + '/system/ping', function (resp, status) { this.emit('ping')
$.get('http://' + _root + '/system/ping', (resp, status) => {
if (status === 200) { if (status === 200) {
clearInterval(pingIv) clearInterval(this.pingInterval)
console.info('Server ready, reloading page...') console.info('Server ready, reloading page...')
this.emit('ping-success')
location.reload() location.reload()
} } else this.emit('ping-fail', status)
}, { }, {
timeout: 100 timeout: 100
}) })
}, 1000) }, 1000)
} }
return {
ws: null,
init,
send: doSend,
canSend // check flood control
}
} }

@ -114,7 +114,8 @@ window.TermScreen = class TermScreen extends EventEmitter {
fitIntoWidth: 0, fitIntoWidth: 0,
fitIntoHeight: 0, fitIntoHeight: 0,
debug: false, debug: false,
graphics: 0 graphics: 0,
statusScreen: null
} }
// scaling caused by fitIntoWidth/fitIntoHeight // scaling caused by fitIntoWidth/fitIntoHeight
@ -879,9 +880,17 @@ window.TermScreen = class TermScreen extends EventEmitter {
height, height,
devicePixelRatio, devicePixelRatio,
gridScaleX, gridScaleX,
gridScaleY gridScaleY,
statusScreen
} = this.window } = this.window
if (statusScreen) {
// draw status screen instead
this.drawStatus(statusScreen)
this.startDrawLoop()
return
} else this.stopDrawLoop()
const charSize = this.getCharSize() const charSize = this.getCharSize()
const { width: cellWidth, height: cellHeight } = this.getCellSize() const { width: cellWidth, height: cellHeight } = this.getCellSize()
const screenWidth = width * cellWidth const screenWidth = width * cellWidth
@ -1085,6 +1094,72 @@ window.TermScreen = class TermScreen extends EventEmitter {
if (this.window.debug && this._debug) this._debug.drawEnd() if (this.window.debug && this._debug) this._debug.drawEnd()
} }
drawStatus (statusScreen) {
const ctx = this.ctx
const {
fontFamily,
width,
height
} = this.window
// reset drawnScreen to force redraw when statusScreen is disabled
this.drawnScreen = []
const cellSize = this.getCellSize()
const screenWidth = width * cellSize.width
const screenHeight = height * cellSize.height
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0)
ctx.clearRect(0, 0, screenWidth, screenHeight)
ctx.font = `40px ${fontFamily}`
ctx.fillStyle = '#fff'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(statusScreen.title || '', screenWidth / 2, screenHeight / 2 - 20)
if (statusScreen.loading) {
// show loading spinner
ctx.save()
ctx.translate(screenWidth / 2, screenHeight / 2 + 50)
ctx.strokeStyle = '#fff'
ctx.lineWidth = 5
ctx.lineCap = 'round'
let t = Date.now() / 1000
for (let i = 0; i < 12; i++) {
ctx.rotate(Math.PI / 6)
let offset = ((t * 12) - i) % 12
ctx.globalAlpha = Math.max(0.2, 1 - offset / 3)
ctx.beginPath()
ctx.moveTo(0, 15)
ctx.lineTo(0, 30)
ctx.stroke()
}
ctx.restore()
}
}
startDrawLoop () {
if (this._drawTimerThread) return
let threadID = Math.random().toString(36)
this._drawTimerThread = threadID
this.drawTimerLoop(threadID)
}
stopDrawLoop () {
this._drawTimerThread = null
}
drawTimerLoop (threadID) {
if (!threadID || threadID !== this._drawTimerThread) return
requestAnimationFrame(() => this.drawTimerLoop(threadID))
this.draw('draw-loop')
}
/** /**
* Parses the content of an `S` message and schedules a draw * Parses the content of an `S` message and schedules a draw
* @param {string} str - the message content * @param {string} str - the message content

Loading…
Cancel
Save