Merge branch 'webpack' into work

box-drawing
Ondřej Hruška 7 years ago
commit b51a2d1b54
  1. 4
      .eslintrc
  2. 24
      _build_js.sh
  3. 2
      dump_js_lang.php
  4. 29
      js/appcommon.js
  5. 6
      js/debug_screen.js
  6. 13
      js/demo.js
  7. 70
      js/event_emitter.js
  8. 14
      js/index.js
  9. 2
      js/lang.js
  10. 2
      js/lib/chibi.js
  11. 74
      js/modal.js
  12. 106
      js/notif.js
  13. 7
      js/soft_keyboard.js
  14. 25
      js/term.js
  15. 17
      js/term_conn.js
  16. 5
      js/term_input.js
  17. 94
      js/term_screen.js
  18. 14
      js/term_upload.js
  19. 57
      js/themes.js
  20. 51
      js/utils.js
  21. 18
      js/wifi.js
  22. 9
      package.json
  23. 5
      pages/_cfg_menu.php
  24. 6
      pages/term.php
  25. 37
      webpack.config.js
  26. 892
      yarn.lock

@ -126,7 +126,7 @@
"no-this-before-super": "error", "no-this-before-super": "error",
"no-throw-literal": "error", "no-throw-literal": "error",
"no-trailing-spaces": "off", "no-trailing-spaces": "off",
"no-undef": "off", "no-undef": "warn",
"no-undef-init": "error", "no-undef-init": "error",
"no-unexpected-multiline": "error", "no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "error", "no-unmodified-loop-condition": "error",
@ -135,7 +135,7 @@
"no-unsafe-finally": "error", "no-unsafe-finally": "error",
"no-unsafe-negation": "error", "no-unsafe-negation": "error",
"no-unused-expressions": ["warn", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], "no-unused-expressions": ["warn", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }],
"no-unused-vars": ["off", { "vars": "local", "args": "none", "ignoreRestSiblings": true }], "no-unused-vars": ["warn", { "vars": "local", "args": "none", "ignoreRestSiblings": true }],
"no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }],
"no-useless-call": "error", "no-useless-call": "error",
"no-useless-computed-key": "error", "no-useless-computed-key": "error",

@ -6,26 +6,4 @@ echo 'Generating lang.js...'
php ./dump_js_lang.php php ./dump_js_lang.php
echo 'Processing JS...' echo 'Processing JS...'
if [[ $ESP_PROD ]]; then npm run webpack
smarg=
demofile=
else
smarg=--source-maps
demofile=js/demo.js
fi
npm run babel -- -o "out/js/app.$FRONT_END_HASH.js" ${smarg} js/lib \
js/lib/chibi.js \
js/lib/polyfills.js \
js/event_emitter.js \
js/utils.js \
js/modal.js \
js/notif.js \
js/appcommon.js \
$demofile \
js/lang.js \
js/wifi.js \
js/term_* \
js/debug_screen.js \
js/soft_keyboard.js \
js/term.js

@ -18,5 +18,5 @@ foreach ($selected as $key) {
file_put_contents(__DIR__. '/js/lang.js', file_put_contents(__DIR__. '/js/lang.js',
"// Generated from PHP locale file\n" . "// Generated from PHP locale file\n" .
'let _tr = ' . json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . ";\n\n" . 'let _tr = ' . json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . ";\n\n" .
"function tr (key) { return _tr[key] || '?' + key + '?' }\n" "module.exports = function tr (key) { return _tr[key] || '?' + key + '?' }\n"
); );

@ -1,5 +1,18 @@
const $ = require('./lib/chibi')
const { mk, qs, cr } = require('./utils')
const modal = require('./modal')
const notify = require('./notif')
/** Global generic init */ /** Global generic init */
$.ready(function () { $.ready(function () {
// Opening menu on mobile / narrow screen
function menuOpen () {
$('#menu').toggleClass('expanded')
}
$('#brand')
.on('click', menuOpen)
.on('keypress', cr(menuOpen))
// Checkbox UI (checkbox CSS and hidden input with int value) // Checkbox UI (checkbox CSS and hidden input with int value)
$('.Row.checkbox').forEach(function (x) { $('.Row.checkbox').forEach(function (x) {
let inp = x.querySelector('input') let inp = x.querySelector('input')
@ -58,8 +71,8 @@ $.ready(function () {
val -= step val -= step
} }
if (undef(min)) val = Math.max(val, +min) if (!Number.isFinite(min)) val = Math.max(val, +min)
if (undef(max)) val = Math.min(val, +max) if (!Number.isFinite(max)) val = Math.min(val, +max)
$this.val(val) $this.val(val)
if ('createEvent' in document) { if ('createEvent' in document) {
@ -75,9 +88,9 @@ $.ready(function () {
// populate the form errors box from GET arg ?err=... // populate the form errors box from GET arg ?err=...
// (a way to pass errors back from server via redirect) // (a way to pass errors back from server via redirect)
let errAt = location.search.indexOf('err=') let errAt = window.location.search.indexOf('err=')
if (errAt !== -1 && qs('.Box.errors')) { if (errAt !== -1 && qs('.Box.errors')) {
let errs = location.search.substr(errAt + 4).split(',') let errs = window.location.search.substr(errAt + 4).split(',')
let humanReadableErrors = [] let humanReadableErrors = []
errs.forEach(function (er) { errs.forEach(function (er) {
let lbl = qs('label[for="' + er + '"]') let lbl = qs('label[for="' + er + '"]')
@ -94,8 +107,8 @@ $.ready(function () {
qs('.Box.errors').classList.remove('hidden') qs('.Box.errors').classList.remove('hidden')
} }
Modal.init() modal.init()
Notify.init() notify.init()
// remove tabindices from h2 if wide // remove tabindices from h2 if wide
if (window.innerWidth > 550) { if (window.innerWidth > 550) {
@ -106,7 +119,7 @@ $.ready(function () {
// brand works as a link back to term in widescreen mode // brand works as a link back to term in widescreen mode
let br = qs('#brand') let br = qs('#brand')
br && br.addEventListener('click', function () { br && br.addEventListener('click', function () {
location.href = '/' // go to terminal window.location.href = '/' // go to terminal
}) })
} }
}) })
@ -122,6 +135,8 @@ function showPage () {
pageShown = true pageShown = true
$('#content').addClass('load') $('#content').addClass('load')
} }
// HACKITY HACK: fix this later
window.showPage = showPage
// Auto reveal pages other than the terminal (sets window.noAutoShow) // Auto reveal pages other than the terminal (sets window.noAutoShow)
$.ready(function () { $.ready(function () {

@ -1,4 +1,6 @@
window.attachDebugScreen = function (screen) { const { mk } = require('./utils')
module.exports = function attachDebugScreen (screen) {
const debugCanvas = mk('canvas') const debugCanvas = mk('canvas')
const ctx = debugCanvas.getContext('2d') const ctx = debugCanvas.getContext('2d')
@ -73,7 +75,7 @@ window.attachDebugScreen = function (screen) {
let isDrawing = false let isDrawing = false
let drawLoop = function () { let drawLoop = function () {
if (isDrawing) requestAnimationFrame(drawLoop) if (isDrawing) window.requestAnimationFrame(drawLoop)
let { devicePixelRatio, width, height } = screen.window let { devicePixelRatio, width, height } = screen.window
let { width: cellWidth, height: cellHeight } = screen.getCellSize() let { width: cellWidth, height: cellHeight } = screen.getCellSize()

@ -1,3 +1,6 @@
const EventEmitter = require('events')
const { encode2B, encode3B, parse2B } = require('./utils')
class ANSIParser { class ANSIParser {
constructor (handler) { constructor (handler) {
this.reset() this.reset()
@ -172,7 +175,7 @@ class ScrollingTerminal {
} }
} }
} }
deleteChar () { deleteChar () { // FIXME unused?
this.moveBack() this.moveBack()
this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE]) this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE])
this.screen.splice(this.cursor.y * this.width + this.cursor.x, 1) this.screen.splice(this.cursor.y * this.width + this.cursor.x, 1)
@ -194,11 +197,11 @@ class ScrollingTerminal {
} else if (action === 'delete') { } else if (action === 'delete') {
this.deleteForward(args[0]) this.deleteForward(args[0])
} else if (action === 'insert-blanks') { } else if (action === 'insert-blanks') {
this.insertBlanks(args[0]) this.insertBlanks(args[0]) // FIXME undefined?
} else if (action === 'clear') { } else if (action === 'clear') {
this.clear() this.clear()
} else if (action === 'bell') { } else if (action === 'bell') {
this.terminal.load('B') this.terminal.load('B') // FIXME undefined?
} else if (action === 'back') { } else if (action === 'back') {
this.moveBack() this.moveBack()
} else if (action === 'new-line') { } else if (action === 'new-line') {
@ -563,7 +566,7 @@ let demoshIndex = {
run (...args) { run (...args) {
let steady = args.includes('--steady') let steady = args.includes('--steady')
if (args.includes('block')) { if (args.includes('block')) {
this.emit('write', `\x1b[${0 + 2 * steady} q`) this.emit('write', `\x1b[${2 * steady} q`)
} else if (args.includes('line')) { } else if (args.includes('line')) {
this.emit('write', `\x1b[${3 + steady} q`) this.emit('write', `\x1b[${3 + steady} q`)
} else if (args.includes('bar') || args.includes('beam')) { } else if (args.includes('bar') || args.includes('beam')) {
@ -862,7 +865,7 @@ class DemoShell {
} }
} }
window.demoInterface = { window.demoInterface = module.exports = {
input (data) { input (data) {
let type = data[0] let type = data[0]
let content = data.substr(1) let content = data.substr(1)

@ -1,70 +0,0 @@
if (!('EventEmitter' in window)) {
window.EventEmitter = class EventEmitter {
constructor () {
this._listeners = {}
}
/**
* Bind an event listener to an event
* @param {string} event - the event name
* @param {Function} listener - the event listener
*/
on (event, listener) {
if (!this._listeners[event]) this._listeners[event] = []
this._listeners[event].push({ listener })
}
/**
* Bind an event listener to be run only once the next time the event fires
* @param {string} event - the event name
* @param {Function} listener - the event listener
*/
once (event, listener) {
if (!this._listeners[event]) this._listeners[event] = []
this._listeners[event].push({ listener, once: true })
}
/**
* Remove an event listener
* @param {string} event - the event name
* @param {Function} listener - the event listener
*/
off (event, listener) {
let listeners = this._listeners[event]
if (listeners) {
for (let i in listeners) {
if (listeners[i].listener === listener) {
listeners.splice(i, 1)
break
}
}
}
}
/**
* Emits an event
* @param {string} event - the event name
* @param {...any} args - arguments passed to all listeners
*/
emit (event, ...args) {
let listeners = this._listeners[event]
if (listeners) {
let remove = []
for (let listener of listeners) {
try {
listener.listener(...args)
if (listener.once) remove.push(listener)
} catch (err) {
console.error(err)
}
}
// this needs to be done in this roundabout way because for loops
// do not like arrays with changing lengths
for (let listener of remove) {
listeners.splice(listeners.indexOf(listener), 1)
}
}
}
}
}

@ -0,0 +1,14 @@
require('./lib/polyfills')
require('./modal')
require('./notif')
require('./appcommon')
try { require('./demo') } catch (err) {}
require('./wifi')
const $ = require('./lib/chibi')
const { qs } = require('./utils')
/* Export stuff to the global scope for inline scripts */
window.termInit = require('./term')
window.$ = $
window.qs = qs

@ -5,4 +5,4 @@ let _tr = {
"wifi.enter_passwd": "Enter password for \":ssid:\"" "wifi.enter_passwd": "Enter password for \":ssid:\""
}; };
function tr (key) { return _tr[key] || '?' + key + '?' } module.exports = function tr (key) { return _tr[key] || '?' + key + '?' }

@ -699,5 +699,5 @@
}; };
// Set Chibi's global namespace here ($) // Set Chibi's global namespace here ($)
w.$ = chibi; module.exports = chibi;
}()); }());

@ -1,44 +1,44 @@
const $ = require('./lib/chibi')
/** Module for toggling a modal overlay */ /** Module for toggling a modal overlay */
(function () { let modal = {}
let modal = {} let curCloseCb = null
let curCloseCb = null
modal.show = function (sel, closeCb) { modal.show = function (sel, closeCb) {
let $m = $(sel) let $m = $(sel)
$m.removeClass('hidden visible') $m.removeClass('hidden visible')
setTimeout(function () { setTimeout(function () {
$m.addClass('visible') $m.addClass('visible')
}, 1) }, 1)
curCloseCb = closeCb curCloseCb = closeCb
} }
modal.hide = function (sel) { modal.hide = function (sel) {
let $m = $(sel) let $m = $(sel)
$m.removeClass('visible') $m.removeClass('visible')
setTimeout(function () { setTimeout(function () {
$m.addClass('hidden') $m.addClass('hidden')
if (curCloseCb) curCloseCb() if (curCloseCb) curCloseCb()
}, 500) // transition time }, 500) // transition time
} }
modal.init = function () { modal.init = function () {
// close modal by click outside the dialog // close modal by click outside the dialog
$('.Modal').on('click', function () { $('.Modal').on('click', function () {
if ($(this).hasClass('no-close')) return // this is a no-close modal if ($(this).hasClass('no-close')) return // this is a no-close modal
modal.hide(this) modal.hide(this)
}) })
$('.Dialog').on('click', function (e) { $('.Dialog').on('click', function (e) {
e.stopImmediatePropagation() e.stopImmediatePropagation()
}) })
// Hide all modals on esc // Hide all modals on esc
$(window).on('keydown', function (e) { $(window).on('keydown', function (e) {
if (e.which === 27) { if (e.which === 27) {
modal.hide('.Modal') modal.hide('.Modal')
} }
}) })
} }
window.Modal = modal module.exports = modal
})()

@ -1,65 +1,65 @@
window.Notify = (function () { const $ = require('./lib/chibi')
let nt = {} const modal = require('./modal')
const sel = '#notif'
let $balloon
let timerHideBegin // timeout to start hiding (transition) let nt = {}
let timerHideEnd // timeout to add the hidden class const sel = '#notif'
let timerCanCancel let $balloon
let canCancel = false
let stopTimeouts = function () { let timerHideBegin // timeout to start hiding (transition)
clearTimeout(timerHideBegin) let timerHideEnd // timeout to add the hidden class
clearTimeout(timerHideEnd) let canCancel = false
}
nt.show = function (message, timeout, isError) {
$balloon.toggleClass('error', isError === true)
$balloon.html(message)
Modal.show($balloon)
stopTimeouts()
if (undef(timeout) || timeout === null || timeout <= 0) { let stopTimeouts = function () {
timeout = 2500 clearTimeout(timerHideBegin)
} clearTimeout(timerHideEnd)
}
timerHideBegin = setTimeout(nt.hide, timeout) nt.show = function (message, timeout, isError) {
$balloon.toggleClass('error', isError === true)
$balloon.html(message)
modal.show($balloon)
stopTimeouts()
canCancel = false if (!timeout || timeout <= 0) {
timerCanCancel = setTimeout(function () { timeout = 2500
canCancel = true
}, 500)
} }
nt.hide = function () { timerHideBegin = setTimeout(nt.hide, timeout)
let $m = $(sel)
$m.removeClass('visible')
timerHideEnd = setTimeout(function () {
$m.addClass('hidden')
}, 250) // transition time
}
nt.init = function () { canCancel = false
$balloon = $(sel) setTimeout(() => {
canCancel = true
}, 500)
}
// close by click outside nt.hide = function () {
$(document).on('click', function () { let $m = $(sel)
if (!canCancel) return $m.removeClass('visible')
nt.hide(this) timerHideEnd = setTimeout(function () {
}) $m.addClass('hidden')
}, 250) // transition time
}
// click caused by selecting, prevent it from bubbling nt.init = function () {
$balloon.on('click', function (e) { $balloon = $(sel)
e.stopImmediatePropagation()
return false
})
// stop fading if moused // close by click outside
$balloon.on('mouseenter', function () { $(document).on('click', function () {
stopTimeouts() if (!canCancel) return
$balloon.removeClass('hidden').addClass('visible') nt.hide(this)
}) })
}
// click caused by selecting, prevent it from bubbling
$balloon.on('click', function (e) {
e.stopImmediatePropagation()
return false
})
// stop fading if moused
$balloon.on('mouseenter', function () {
stopTimeouts()
$balloon.removeClass('hidden').addClass('visible')
})
}
return nt module.exports = nt
})()

@ -1,4 +1,6 @@
window.initSoftKeyboard = function (screen, input) { const { qs } = require('./utils')
module.exports = function (screen, input) {
const keyInput = qs('#softkb-input') const keyInput = qs('#softkb-input')
if (!keyInput) return // abort, we're not on the terminal page if (!keyInput) return // abort, we're not on the terminal page
@ -33,7 +35,6 @@ window.initSoftKeyboard = function (screen, input) {
// that deals with the input composition events. // that deals with the input composition events.
let lastCompositionString = '' let lastCompositionString = ''
let compositing = false
// sends the difference between the last and the new composition string // sends the difference between the last and the new composition string
let sendInputDelta = function (newValue) { let sendInputDelta = function (newValue) {
@ -96,12 +97,10 @@ window.initSoftKeyboard = function (screen, input) {
keyInput.addEventListener('compositionstart', e => { keyInput.addEventListener('compositionstart', e => {
lastCompositionString = '' lastCompositionString = ''
compositing = true
}) })
keyInput.addEventListener('compositionend', e => { keyInput.addEventListener('compositionend', e => {
lastCompositionString = '' lastCompositionString = ''
compositing = false
keyInput.value = '' keyInput.value = ''
}) })

@ -1,9 +1,18 @@
const { qs, mk } = require('./utils')
const Notify = require('./notif')
const TermScreen = require('./term_screen')
const TermConnection = require('./term_conn')
const TermInput = require('./term_input')
const TermUpload = require('./term_upload')
const initSoftKeyboard = require('./soft_keyboard')
const attachDebugScreen = require('./debug_screen')
/** Init the terminal sub-module - called from HTML */ /** Init the terminal sub-module - called from HTML */
window.termInit = function ({ labels, theme, allFn }) { module.exports = function ({ labels, theme, allFn }) {
const screen = new TermScreen() const screen = new TermScreen()
const conn = new Conn(screen) const conn = new TermConnection(screen)
const input = Input(conn, screen) const input = TermInput(conn, screen)
const termUpload = TermUpl(conn, input, screen) const termUpload = TermUpload(conn, input, screen)
screen.input = input screen.input = input
input.termUpload = termUpload input.termUpload = termUpload
@ -39,8 +48,8 @@ window.termInit = function ({ labels, theme, allFn }) {
qs('#screen').appendChild(screen.canvas) qs('#screen').appendChild(screen.canvas)
screen.load(labels, theme) // load labels and theme screen.load(labels, theme) // load labels and theme
window.initSoftKeyboard(screen, input) initSoftKeyboard(screen, input)
if (window.attachDebugScreen) window.attachDebugScreen(screen) if (attachDebugScreen) attachDebugScreen(screen)
let isFullscreen = false let isFullscreen = false
let fitScreen = false let fitScreen = false
@ -75,10 +84,10 @@ window.termInit = function ({ labels, theme, allFn }) {
}) })
// add fullscreen mode & button // add fullscreen mode & button
if (Element.prototype.requestFullscreen || Element.prototype.webkitRequestFullscreen) { if (window.Element.prototype.requestFullscreen || window.Element.prototype.webkitRequestFullscreen) {
let checkForFullscreen = function () { let checkForFullscreen = function () {
// document.fullscreenElement is not really supported yet, so here's a hack // document.fullscreenElement is not really supported yet, so here's a hack
if (isFullscreen && (innerWidth !== window.screen.width || innerHeight !== window.screen.height)) { if (isFullscreen && (window.innerWidth !== window.screen.width || window.innerHeight !== window.screen.height)) {
isFullscreen = false isFullscreen = false
fitScreenIfNeeded() fitScreenIfNeeded()
} }

@ -1,5 +1,10 @@
const EventEmitter = require('events')
const $ = require('./lib/chibi')
let demo
try { demo = require('./demo') } catch (err) {}
/** Handle connections */ /** Handle connections */
window.Conn = class TermConnection extends EventEmitter { module.exports = class TermConnection extends EventEmitter {
constructor (screen) { constructor (screen) {
super() super()
@ -94,7 +99,7 @@ window.Conn = class TermConnection extends EventEmitter {
send (message) { send (message) {
if (window._demo) { if (window._demo) {
if (typeof window.demoInterface !== 'undefined') { if (typeof window.demoInterface !== 'undefined') {
demoInterface.input(message) demo.input(message)
} else { } else {
console.log(`TX: ${JSON.stringify(message)}`) console.log(`TX: ${JSON.stringify(message)}`)
} }
@ -130,9 +135,9 @@ window.Conn = class TermConnection extends EventEmitter {
init () { init () {
if (window._demo) { if (window._demo) {
if (typeof window.demoInterface === 'undefined') { if (typeof window.demoInterface === 'undefined') {
alert('Demoing non-demo build!') // this will catch mistakes when deploying to the website window.alert('Demoing non-demo build!') // this will catch mistakes when deploying to the website
} else { } else {
demoInterface.init(this.screen) demo.init(this.screen)
showPage() showPage()
} }
return return
@ -143,7 +148,7 @@ window.Conn = class TermConnection extends EventEmitter {
this.closeSocket() this.closeSocket()
this.ws = new WebSocket('ws://' + _root + '/term/update.ws') this.ws = new window.WebSocket('ws://' + window._root + '/term/update.ws')
this.ws.addEventListener('open', (...args) => this.onWSOpen(...args)) this.ws.addEventListener('open', (...args) => this.onWSOpen(...args))
this.ws.addEventListener('close', (...args) => this.onWSClose(...args)) this.ws.addEventListener('close', (...args) => this.onWSClose(...args))
this.ws.addEventListener('message', (...args) => this.onWSMessage(...args)) this.ws.addEventListener('message', (...args) => this.onWSMessage(...args))
@ -167,7 +172,7 @@ window.Conn = class TermConnection extends EventEmitter {
this.pingInterval = setInterval(() => { this.pingInterval = setInterval(() => {
console.log('> ping') console.log('> ping')
this.emit('ping') this.emit('ping')
$.get('http://' + _root + '/system/ping', (resp, status) => { $.get('http://' + window._root + '/system/ping', (resp, status) => {
if (status === 200) { if (status === 200) {
clearInterval(this.pingInterval) clearInterval(this.pingInterval)
console.info('Server ready, opening socket…') console.info('Server ready, opening socket…')

@ -1,3 +1,6 @@
const $ = require('./lib/chibi')
const { encode2B } = require('./utils')
/** /**
* User input * User input
* *
@ -14,7 +17,7 @@
* r - mb release * r - mb release
* m - mouse move * m - mouse move
*/ */
window.Input = function (conn, screen) { module.exports = function (conn, screen) {
// handle for input object // handle for input object
let input let input

@ -1,3 +1,9 @@
const EventEmitter = require('events')
const $ = require('./lib/chibi')
const { mk, qs, parse2B, parse3B } = require('./utils')
const notify = require('./notif')
const { themes, buildColorTable } = require('./themes')
// constants for decoding the update blob // constants for decoding the update blob
const SEQ_REPEAT = 2 const SEQ_REPEAT = 2
const SEQ_SET_COLORS = 3 const SEQ_SET_COLORS = 3
@ -8,7 +14,7 @@ const SEQ_SET_BG = 6
const SELECTION_BG = '#b2d7fe' const SELECTION_BG = '#b2d7fe'
const SELECTION_FG = '#333' const SELECTION_FG = '#333'
window.TermScreen = class TermScreen extends EventEmitter { module.exports = class TermScreen extends EventEmitter {
constructor () { constructor () {
super() super()
@ -21,54 +27,9 @@ window.TermScreen = class TermScreen extends EventEmitter {
'Z': '\u2128' 'Z': '\u2128'
} }
this.themes = [
[ // Tango
'#111213', '#CC0000', '#4E9A06', '#C4A000', '#3465A4', '#75507B', '#06989A', '#D3D7CF',
'#555753', '#EF2929', '#8AE234', '#FCE94F', '#729FCF', '#AD7FA8', '#34E2E2', '#EEEEEC'
],
[ // Linux
'#000000', '#aa0000', '#00aa00', '#aa5500', '#0000aa', '#aa00aa', '#00aaaa', '#aaaaaa',
'#555555', '#ff5555', '#55ff55', '#ffff55', '#5555ff', '#ff55ff', '#55ffff', '#ffffff'
],
[ // xterm
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5',
'#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'
],
[ // rxvt
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000cd', '#cd00cd', '#00cdcd', '#faebd7',
'#404040', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff'
],
[ // Ambience
'#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf',
'#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'
],
[ // Solarized
'#073642', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#eee8d5',
'#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1', '#fdf6e3'
]
]
// 256color lookup table // 256color lookup table
// should not be used to look up 0-15 (will return transparent) // should not be used to look up 0-15 (will return transparent)
this.colorTable256 = new Array(16).fill('rgba(0, 0, 0, 0)') this.colorTable256 = buildColorTable()
// fill color table
// colors 16-231 are a 6x6x6 color cube
for (let red = 0; red < 6; red++) {
for (let green = 0; green < 6; green++) {
for (let blue = 0; blue < 6; blue++) {
let redValue = red * 40 + (red ? 55 : 0)
let greenValue = green * 40 + (green ? 55 : 0)
let blueValue = blue * 40 + (blue ? 55 : 0)
this.colorTable256.push(`rgb(${redValue}, ${greenValue}, ${blueValue})`)
}
}
}
// colors 232-255 are a grayscale ramp, sans black and white
for (let gray = 0; gray < 24; gray++) {
let value = gray * 10 + 8
this.colorTable256.push(`rgb(${value}, ${value}, ${value})`)
}
this._debug = null this._debug = null
@ -351,7 +312,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
* @type {number[]} * @type {number[]}
*/ */
get palette () { get palette () {
return this._palette || this.themes[0] return this._palette || themes[0]
} }
/** @type {number[]} */ /** @type {number[]} */
set palette (palette) { set palette (palette) {
@ -472,8 +433,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
const { const {
width, width,
height, height,
gridScaleX,
gridScaleY,
fitIntoWidth, fitIntoWidth,
fitIntoHeight fitIntoHeight
} = this.window } = this.window
@ -632,9 +591,9 @@ window.TermScreen = class TermScreen extends EventEmitter {
textarea.value = selectedText textarea.value = selectedText
textarea.select() textarea.select()
if (document.execCommand('copy')) { if (document.execCommand('copy')) {
Notify.show('Copied to clipboard') notify.show('Copied to clipboard')
} else { } else {
Notify.show('Failed to copy') notify.show('Failed to copy')
} }
document.body.removeChild(textarea) document.body.removeChild(textarea)
} }
@ -899,8 +858,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
width, width,
height, height,
devicePixelRatio, devicePixelRatio,
gridScaleX,
gridScaleY,
statusScreen statusScreen
} = this.window } = this.window
@ -913,8 +870,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
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 screenHeight = height * cellHeight
const screenLength = width * height const screenLength = width * height
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0)
@ -1042,7 +997,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
// pass 1: backgrounds // pass 1: backgrounds
for (let font of fontGroups.keys()) { for (let font of fontGroups.keys()) {
for (let data of fontGroups.get(font)) { for (let data of fontGroups.get(font)) {
let [cell, x, y, text, fg, bg, attrs, isCursor] = data let [cell, x, y, text, , bg] = data
if (redrawMap.get(cell)) { if (redrawMap.get(cell)) {
this.drawBackground({ x, y, cellWidth, cellHeight, bg }) this.drawBackground({ x, y, cellWidth, cellHeight, bg })
@ -1128,7 +1083,8 @@ window.TermScreen = class TermScreen extends EventEmitter {
const { const {
fontFamily, fontFamily,
width, width,
height height,
devicePixelRatio
} = this.window } = this.window
// reset drawnScreen to force redraw when statusScreen is disabled // reset drawnScreen to force redraw when statusScreen is disabled
@ -1185,7 +1141,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
drawTimerLoop (threadID) { drawTimerLoop (threadID) {
if (!threadID || threadID !== this._drawTimerThread) return if (!threadID || threadID !== this._drawTimerThread) return
requestAnimationFrame(() => this.drawTimerLoop(threadID)) window.requestAnimationFrame(() => this.drawTimerLoop(threadID))
this.draw('draw-loop') this.draw('draw-loop')
} }
@ -1296,7 +1252,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
this.screenAttrs = new Array(screenLength).fill(' ') this.screenAttrs = new Array(screenLength).fill(' ')
} }
let strArray = !undef(Array.from) ? Array.from(str) : str.split('') let strArray = Array.from ? Array.from(str) : str.split('')
const MASK_LINE_ATTR = 0xC8 const MASK_LINE_ATTR = 0xC8
const MASK_BLINK = 1 << 4 const MASK_BLINK = 1 << 4
@ -1392,7 +1348,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
let label = pieces[i + 1].trim() let label = pieces[i + 1].trim()
// if empty string, use the "dim" effect and put nbsp instead to // if empty string, use the "dim" effect and put nbsp instead to
// stretch the button vertically // stretch the button vertically
button.innerHTML = label ? esc(label) : '&nbsp;' button.innerHTML = label ? $.htmlEscape(label) : '&nbsp;'
button.style.opacity = label ? 1 : 0.2 button.style.opacity = label ? 1 : 0.2
}) })
} }
@ -1403,17 +1359,17 @@ window.TermScreen = class TermScreen extends EventEmitter {
*/ */
showNotification (text) { showNotification (text) {
console.info(`Notification: ${text}`) console.info(`Notification: ${text}`)
if (Notification && Notification.permission === 'granted') { if (window.Notification && window.Notification.permission === 'granted') {
let notification = new Notification('ESPTerm', { let notification = new window.Notification('ESPTerm', {
body: text body: text
}) })
notification.addEventListener('click', () => window.focus()) notification.addEventListener('click', () => window.focus())
} else { } else {
if (Notification && Notification.permission !== 'denied') { if (window.Notification && window.Notification.permission !== 'denied') {
Notification.requestPermission() window.Notification.requestPermission()
} else { } else {
// Fallback using the built-in notification balloon // Fallback using the built-in notification balloon
Notify.show(text) notify.show(text)
} }
} }
} }
@ -1425,8 +1381,8 @@ window.TermScreen = class TermScreen extends EventEmitter {
*/ */
load (str, theme = -1) { load (str, theme = -1) {
const content = str.substr(1) const content = str.substr(1)
if (theme >= 0 && theme < this.themes.length) { if (theme >= 0 && theme < themes.length) {
this.palette = this.themes[theme] this.palette = themes[theme]
} }
switch (str[0]) { switch (str[0]) {
@ -1500,7 +1456,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
surrOsc.stop(startTime + 0.8) surrOsc.stop(startTime + 0.8)
let loop = function () { let loop = function () {
if (audioCtx.currentTime < startTime + 0.8) requestAnimationFrame(loop) if (audioCtx.currentTime < startTime + 0.8) window.requestAnimationFrame(loop)
mainGain.gain.value *= 0.8 mainGain.gain.value *= 0.8
surrGain.gain.value *= 0.8 surrGain.gain.value *= 0.8
} }

@ -1,5 +1,9 @@
const $ = require('./lib/chibi')
const { qs } = require('./utils')
const modal = require('./modal')
/** File upload utility */ /** File upload utility */
window.TermUpl = function (conn, input, screen) { module.exports = function (conn, input, screen) {
let lines, // array of lines without newlines let lines, // array of lines without newlines
line_i, // current line index line_i, // current line index
fuTout, // timeout handle for line sending fuTout, // timeout handle for line sending
@ -14,7 +18,7 @@ window.TermUpl = function (conn, input, screen) {
function openUploadDialog () { function openUploadDialog () {
updateStatus('Ready...') updateStatus('Ready...')
Modal.show('#fu_modal', onDialogClose) modal.show('#fu_modal', onDialogClose)
$('#fu_form').toggleClass('busy', false) $('#fu_form').toggleClass('busy', false)
input.blockKeys(true) input.blockKeys(true)
} }
@ -125,19 +129,19 @@ window.TermUpl = function (conn, input, screen) {
} }
function fuClose () { function fuClose () {
Modal.hide('#fu_modal') modal.hide('#fu_modal')
} }
return { return {
init: function () { init: function () {
qs('#fu_file').addEventListener('change', function (evt) { qs('#fu_file').addEventListener('change', function (evt) {
let reader = new FileReader() let reader = new window.FileReader()
let file = evt.target.files[0] let file = evt.target.files[0]
let ftype = file.type || 'application/octet-stream' let ftype = file.type || 'application/octet-stream'
console.log('Selected file type: ' + ftype) console.log('Selected file type: ' + ftype)
if (!ftype.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*|x-php)/)) { if (!ftype.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*|x-php)/)) {
// Deny load of blobs like img - can crash browser and will get corrupted anyway // Deny load of blobs like img - can crash browser and will get corrupted anyway
if (!confirm(`This does not look like a text file: ${ftype}\nReally load?`)) { if (!window.confirm(`This does not look like a text file: ${ftype}\nReally load?`)) {
qs('#fu_file').value = '' qs('#fu_file').value = ''
return return
} }

@ -0,0 +1,57 @@
exports.themes = [
[ // Tango
'#111213', '#CC0000', '#4E9A06', '#C4A000', '#3465A4', '#75507B', '#06989A', '#D3D7CF',
'#555753', '#EF2929', '#8AE234', '#FCE94F', '#729FCF', '#AD7FA8', '#34E2E2', '#EEEEEC'
],
[ // Linux
'#000000', '#aa0000', '#00aa00', '#aa5500', '#0000aa', '#aa00aa', '#00aaaa', '#aaaaaa',
'#555555', '#ff5555', '#55ff55', '#ffff55', '#5555ff', '#ff55ff', '#55ffff', '#ffffff'
],
[ // xterm
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5',
'#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff'
],
[ // rxvt
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000cd', '#cd00cd', '#00cdcd', '#faebd7',
'#404040', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff'
],
[ // Ambience
'#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf',
'#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec'
],
[ // Solarized
'#073642', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#eee8d5',
'#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1', '#fdf6e3'
]
]
let colorTable256 = null
exports.buildColorTable = function () {
if (colorTable256 !== null) return colorTable256
// 256color lookup table
// should not be used to look up 0-15 (will return transparent)
colorTable256 = new Array(16).fill('rgba(0, 0, 0, 0)')
// fill color table
// colors 16-231 are a 6x6x6 color cube
for (let red = 0; red < 6; red++) {
for (let green = 0; green < 6; green++) {
for (let blue = 0; blue < 6; blue++) {
let redValue = red * 40 + (red ? 55 : 0)
let greenValue = green * 40 + (green ? 55 : 0)
let blueValue = blue * 40 + (blue ? 55 : 0)
colorTable256.push(`rgb(${redValue}, ${greenValue}, ${blueValue})`)
}
}
}
// colors 232-255 are a grayscale ramp, sans black and white
for (let gray = 0; gray < 24; gray++) {
let value = gray * 10 + 8
colorTable256.push(`rgb(${value}, ${value}, ${value})`)
}
return colorTable256
}

@ -1,29 +1,24 @@
/** Make a node */ /** Make a node */
function mk (e) { exports.mk = function mk (e) {
return document.createElement(e) return document.createElement(e)
} }
/** Find one by query */ /** Find one by query */
function qs (s) { exports.qs = function qs (s) {
return document.querySelector(s) return document.querySelector(s)
} }
/** Find all by query */ /** Find all by query */
function qsa (s) { exports.qsa = function qsa (s) {
return document.querySelectorAll(s) return document.querySelectorAll(s)
} }
/** Convert any to bool safely */
function bool (x) {
return (x === 1 || x === '1' || x === true || x === 'true')
}
/** /**
* Filter 'spacebar' and 'return' from keypress handler, * Filter 'spacebar' and 'return' from keypress handler,
* and when they're pressed, fire the callback. * and when they're pressed, fire the callback.
* use $(...).on('keypress', cr(handler)) * use $(...).on('keypress', cr(handler))
*/ */
function cr (hdl) { exports.cr = function cr (hdl) {
return function (e) { return function (e) {
if (e.which === 10 || e.which === 13 || e.which === 32) { if (e.which === 10 || e.which === 13 || e.which === 32) {
hdl() hdl()
@ -31,53 +26,33 @@ function cr (hdl) {
} }
} }
/** HTML escape */ /** Convert any to bool safely */
function esc (str) { exports.bool = function bool (x) {
return $.htmlEscape(str) return (x === 1 || x === '1' || x === true || x === 'true')
}
/** Check for undefined */
function undef (x) {
return typeof x == 'undefined'
}
/** Safe json parse */
function jsp (str) {
try {
return JSON.parse(str)
} catch (e) {
console.error(e)
return null
}
}
/** Create a character from ASCII code */
function Chr (n) {
return String.fromCharCode(n)
} }
/** Decode number from 2B encoding */ /** Decode number from 2B encoding */
function parse2B (s, i = 0) { exports.parse2B = function parse2B (s, i = 0) {
return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127 return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127
} }
/** Decode number from 3B encoding */ /** Decode number from 3B encoding */
function parse3B (s, i = 0) { exports.parse3B = function parse3B (s, i = 0) {
return (s.charCodeAt(i) - 1) + (s.charCodeAt(i + 1) - 1) * 127 + (s.charCodeAt(i + 2) - 1) * 127 * 127 return (s.charCodeAt(i) - 1) + (s.charCodeAt(i + 1) - 1) * 127 + (s.charCodeAt(i + 2) - 1) * 127 * 127
} }
/** Encode using 2B encoding, returns string. */ /** Encode using 2B encoding, returns string. */
function encode2B (n) { exports.encode2B = function encode2B (n) {
let lsb, msb let lsb, msb
lsb = (n % 127) lsb = (n % 127)
n = ((n - lsb) / 127) n = ((n - lsb) / 127)
lsb += 1 lsb += 1
msb = (n + 1) msb = (n + 1)
return Chr(lsb) + Chr(msb) return String.fromCharCode(lsb) + String.fromCharCode(msb)
} }
/** Encode using 3B encoding, returns string. */ /** Encode using 3B encoding, returns string. */
function encode3B (n) { exports.encode3B = function encode3B (n) {
let lsb, msb, xsb let lsb, msb, xsb
lsb = (n % 127) lsb = (n % 127)
n = (n - lsb) / 127 n = (n - lsb) / 127
@ -86,5 +61,5 @@ function encode3B (n) {
n = (n - msb) / 127 n = (n - msb) / 127
msb += 1 msb += 1
xsb = (n + 1) xsb = (n + 1)
return Chr(lsb) + Chr(msb) + Chr(xsb) return String.fromCharCode(lsb) + String.fromCharCode(msb) + String.fromCharCode(xsb)
} }

@ -1,4 +1,8 @@
(function (w) { const $ = require('./lib/chibi')
const { mk, bool } = require('./utils')
const tr = require('./lang')
;(function (w) {
const authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2'] const authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2']
let curSSID let curSSID
@ -15,8 +19,8 @@
$('#sta-nw').toggleClass('hidden', name.length === 0) $('#sta-nw').toggleClass('hidden', name.length === 0)
$('#sta-nw-nil').toggleClass('hidden', name.length > 0) $('#sta-nw-nil').toggleClass('hidden', name.length > 0)
$('#sta-nw .essid').html(esc(name)) $('#sta-nw .essid').html($.htmlEscape(name))
const nopw = undef(password) || password.length === 0 const nopw = !password || password.length === 0
$('#sta-nw .passwd').toggleClass('hidden', nopw) $('#sta-nw .passwd').toggleClass('hidden', nopw)
$('#sta-nw .nopasswd').toggleClass('hidden', !nopw) $('#sta-nw .nopasswd').toggleClass('hidden', !nopw)
$('#sta-nw .ip').html(ip.length > 0 ? tr('wifi.connected_ip_is') + ip : tr('wifi.not_conn')) $('#sta-nw .ip').html(ip.length > 0 ? tr('wifi.connected_ip_is') + ip : tr('wifi.not_conn'))
@ -96,7 +100,7 @@
if (+$th.data('pwd')) { if (+$th.data('pwd')) {
// this AP needs a password // this AP needs a password
conn_pass = prompt(tr('wifi.enter_passwd').replace(':ssid:', conn_ssid)) conn_pass = window.prompt(tr('wifi.enter_passwd').replace(':ssid:', conn_ssid))
if (!conn_pass) return if (!conn_pass) return
} }
@ -120,10 +124,10 @@
/** Ask the CGI what APs are visible (async) */ /** Ask the CGI what APs are visible (async) */
function scanAPs () { function scanAPs () {
if (_demo) { if (window._demo) {
onScan(_demo_aps, 200) onScan(window._demo_aps, 200)
} else { } else {
$.get('http://' + _root + '/cfg/wifi/scan', onScan) $.get('http://' + window._root + '/cfg/wifi/scan', onScan)
} }
} }

@ -5,14 +5,15 @@
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-minify": "^0.2.0", "babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"babel-preset-minify": "^0.2.0",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
"standard": "^10.0.3" "standard": "^10.0.3",
"webpack": "^3.6.0"
}, },
"scripts": { "scripts": {
"babel": "babel $@", "webpack": "webpack --display-modules $@",
"minify": "babel-minify $@",
"sass": "node-sass $@" "sass": "node-sass $@"
} }
} }

@ -13,8 +13,3 @@
} }
?> ?>
</nav> </nav>
<script>
function menuOpen() { $('#menu').toggleClass('expanded') }
$('#brand').on('click', menuOpen).on('keypress', cr(menuOpen));
</script>

@ -2,9 +2,9 @@
<script> <script>
// Workaround for badly loaded page // Workaround for badly loaded page
setTimeout(function() { setTimeout(function() {
if (typeof termInit == 'undefined' || typeof $ == 'undefined') { if (typeof termInit == 'undefined') {
console.error("Page load failed, refreshing…"); console.error("Page load failed, refreshing…")
location.reload(true); location.reload(true)
} }
}, 3000); }, 3000);
</script> </script>

@ -0,0 +1,37 @@
const webpack = require('webpack')
const { execSync } = require('child_process')
const path = require('path')
let hash = execSync('git rev-parse --short HEAD').toString().trim()
let plugins = [new webpack.optimize.UglifyJsPlugin()]
let devtool = 'source-map'
if (process.env.ESP_PROD) {
// ignore demo
plugins.push(new webpack.IgnorePlugin(/\.\/demo(?:\.js)?$/))
// no source maps
devtool = ''
}
module.exports = {
entry: './js',
output: {
path: path.resolve(__dirname, 'out', 'js'),
filename: `app.${hash}.js`
},
module: {
rules: [
{
test: /\.js$/,
exclude: [
path.resolve(__dirname, 'node_modules')
],
loader: 'babel-loader'
}
]
},
devtool,
plugins
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save