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-throw-literal": "error",
"no-trailing-spaces": "off",
"no-undef": "off",
"no-undef": "warn",
"no-undef-init": "error",
"no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "error",
@ -135,7 +135,7 @@
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"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-useless-call": "error",
"no-useless-computed-key": "error",

@ -6,26 +6,4 @@ echo 'Generating lang.js...'
php ./dump_js_lang.php
echo 'Processing JS...'
if [[ $ESP_PROD ]]; then
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
npm run webpack

@ -18,5 +18,5 @@ foreach ($selected as $key) {
file_put_contents(__DIR__. '/js/lang.js',
"// Generated from PHP locale file\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 */
$.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)
$('.Row.checkbox').forEach(function (x) {
let inp = x.querySelector('input')
@ -58,8 +71,8 @@ $.ready(function () {
val -= step
}
if (undef(min)) val = Math.max(val, +min)
if (undef(max)) val = Math.min(val, +max)
if (!Number.isFinite(min)) val = Math.max(val, +min)
if (!Number.isFinite(max)) val = Math.min(val, +max)
$this.val(val)
if ('createEvent' in document) {
@ -75,9 +88,9 @@ $.ready(function () {
// populate the form errors box from GET arg ?err=...
// (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')) {
let errs = location.search.substr(errAt + 4).split(',')
let errs = window.location.search.substr(errAt + 4).split(',')
let humanReadableErrors = []
errs.forEach(function (er) {
let lbl = qs('label[for="' + er + '"]')
@ -94,8 +107,8 @@ $.ready(function () {
qs('.Box.errors').classList.remove('hidden')
}
Modal.init()
Notify.init()
modal.init()
notify.init()
// remove tabindices from h2 if wide
if (window.innerWidth > 550) {
@ -106,7 +119,7 @@ $.ready(function () {
// brand works as a link back to term in widescreen mode
let br = qs('#brand')
br && br.addEventListener('click', function () {
location.href = '/' // go to terminal
window.location.href = '/' // go to terminal
})
}
})
@ -122,6 +135,8 @@ function showPage () {
pageShown = true
$('#content').addClass('load')
}
// HACKITY HACK: fix this later
window.showPage = showPage
// Auto reveal pages other than the terminal (sets window.noAutoShow)
$.ready(function () {

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

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

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

@ -1,65 +1,65 @@
window.Notify = (function () {
let nt = {}
const sel = '#notif'
let $balloon
const $ = require('./lib/chibi')
const modal = require('./modal')
let timerHideBegin // timeout to start hiding (transition)
let timerHideEnd // timeout to add the hidden class
let timerCanCancel
let canCancel = false
let nt = {}
const sel = '#notif'
let $balloon
let stopTimeouts = function () {
clearTimeout(timerHideBegin)
clearTimeout(timerHideEnd)
}
nt.show = function (message, timeout, isError) {
$balloon.toggleClass('error', isError === true)
$balloon.html(message)
Modal.show($balloon)
stopTimeouts()
let timerHideBegin // timeout to start hiding (transition)
let timerHideEnd // timeout to add the hidden class
let canCancel = false
if (undef(timeout) || timeout === null || timeout <= 0) {
timeout = 2500
}
let stopTimeouts = function () {
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
timerCanCancel = setTimeout(function () {
canCancel = true
}, 500)
if (!timeout || timeout <= 0) {
timeout = 2500
}
nt.hide = function () {
let $m = $(sel)
$m.removeClass('visible')
timerHideEnd = setTimeout(function () {
$m.addClass('hidden')
}, 250) // transition time
}
timerHideBegin = setTimeout(nt.hide, timeout)
nt.init = function () {
$balloon = $(sel)
canCancel = false
setTimeout(() => {
canCancel = true
}, 500)
}
// close by click outside
$(document).on('click', function () {
if (!canCancel) return
nt.hide(this)
})
nt.hide = function () {
let $m = $(sel)
$m.removeClass('visible')
timerHideEnd = setTimeout(function () {
$m.addClass('hidden')
}, 250) // transition time
}
// click caused by selecting, prevent it from bubbling
$balloon.on('click', function (e) {
e.stopImmediatePropagation()
return false
})
nt.init = function () {
$balloon = $(sel)
// stop fading if moused
$balloon.on('mouseenter', function () {
stopTimeouts()
$balloon.removeClass('hidden').addClass('visible')
})
}
// close by click outside
$(document).on('click', function () {
if (!canCancel) return
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')
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.
let lastCompositionString = ''
let compositing = false
// sends the difference between the last and the new composition string
let sendInputDelta = function (newValue) {
@ -96,12 +97,10 @@ window.initSoftKeyboard = function (screen, input) {
keyInput.addEventListener('compositionstart', e => {
lastCompositionString = ''
compositing = true
})
keyInput.addEventListener('compositionend', e => {
lastCompositionString = ''
compositing = false
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 */
window.termInit = function ({ labels, theme, allFn }) {
module.exports = function ({ labels, theme, allFn }) {
const screen = new TermScreen()
const conn = new Conn(screen)
const input = Input(conn, screen)
const termUpload = TermUpl(conn, input, screen)
const conn = new TermConnection(screen)
const input = TermInput(conn, screen)
const termUpload = TermUpload(conn, input, screen)
screen.input = input
input.termUpload = termUpload
@ -39,8 +48,8 @@ window.termInit = function ({ labels, theme, allFn }) {
qs('#screen').appendChild(screen.canvas)
screen.load(labels, theme) // load labels and theme
window.initSoftKeyboard(screen, input)
if (window.attachDebugScreen) window.attachDebugScreen(screen)
initSoftKeyboard(screen, input)
if (attachDebugScreen) attachDebugScreen(screen)
let isFullscreen = false
let fitScreen = false
@ -75,10 +84,10 @@ window.termInit = function ({ labels, theme, allFn }) {
})
// add fullscreen mode & button
if (Element.prototype.requestFullscreen || Element.prototype.webkitRequestFullscreen) {
if (window.Element.prototype.requestFullscreen || window.Element.prototype.webkitRequestFullscreen) {
let checkForFullscreen = function () {
// 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
fitScreenIfNeeded()
}

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

@ -1,3 +1,6 @@
const $ = require('./lib/chibi')
const { encode2B } = require('./utils')
/**
* User input
*
@ -14,7 +17,7 @@
* r - mb release
* m - mouse move
*/
window.Input = function (conn, screen) {
module.exports = function (conn, screen) {
// handle for input object
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
const SEQ_REPEAT = 2
const SEQ_SET_COLORS = 3
@ -8,7 +14,7 @@ const SEQ_SET_BG = 6
const SELECTION_BG = '#b2d7fe'
const SELECTION_FG = '#333'
window.TermScreen = class TermScreen extends EventEmitter {
module.exports = class TermScreen extends EventEmitter {
constructor () {
super()
@ -21,54 +27,9 @@ window.TermScreen = class TermScreen extends EventEmitter {
'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
// should not be used to look up 0-15 (will return transparent)
this.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)
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.colorTable256 = buildColorTable()
this._debug = null
@ -351,7 +312,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
* @type {number[]}
*/
get palette () {
return this._palette || this.themes[0]
return this._palette || themes[0]
}
/** @type {number[]} */
set palette (palette) {
@ -472,8 +433,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
const {
width,
height,
gridScaleX,
gridScaleY,
fitIntoWidth,
fitIntoHeight
} = this.window
@ -632,9 +591,9 @@ window.TermScreen = class TermScreen extends EventEmitter {
textarea.value = selectedText
textarea.select()
if (document.execCommand('copy')) {
Notify.show('Copied to clipboard')
notify.show('Copied to clipboard')
} else {
Notify.show('Failed to copy')
notify.show('Failed to copy')
}
document.body.removeChild(textarea)
}
@ -899,8 +858,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
width,
height,
devicePixelRatio,
gridScaleX,
gridScaleY,
statusScreen
} = this.window
@ -913,8 +870,6 @@ window.TermScreen = class TermScreen extends EventEmitter {
const charSize = this.getCharSize()
const { width: cellWidth, height: cellHeight } = this.getCellSize()
const screenWidth = width * cellWidth
const screenHeight = height * cellHeight
const screenLength = width * height
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0)
@ -1042,7 +997,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
// pass 1: backgrounds
for (let font of fontGroups.keys()) {
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)) {
this.drawBackground({ x, y, cellWidth, cellHeight, bg })
@ -1128,7 +1083,8 @@ window.TermScreen = class TermScreen extends EventEmitter {
const {
fontFamily,
width,
height
height,
devicePixelRatio
} = this.window
// reset drawnScreen to force redraw when statusScreen is disabled
@ -1185,7 +1141,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
drawTimerLoop (threadID) {
if (!threadID || threadID !== this._drawTimerThread) return
requestAnimationFrame(() => this.drawTimerLoop(threadID))
window.requestAnimationFrame(() => this.drawTimerLoop(threadID))
this.draw('draw-loop')
}
@ -1296,7 +1252,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
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_BLINK = 1 << 4
@ -1392,7 +1348,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
let label = pieces[i + 1].trim()
// if empty string, use the "dim" effect and put nbsp instead to
// stretch the button vertically
button.innerHTML = label ? esc(label) : '&nbsp;'
button.innerHTML = label ? $.htmlEscape(label) : '&nbsp;'
button.style.opacity = label ? 1 : 0.2
})
}
@ -1403,17 +1359,17 @@ window.TermScreen = class TermScreen extends EventEmitter {
*/
showNotification (text) {
console.info(`Notification: ${text}`)
if (Notification && Notification.permission === 'granted') {
let notification = new Notification('ESPTerm', {
if (window.Notification && window.Notification.permission === 'granted') {
let notification = new window.Notification('ESPTerm', {
body: text
})
notification.addEventListener('click', () => window.focus())
} else {
if (Notification && Notification.permission !== 'denied') {
Notification.requestPermission()
if (window.Notification && window.Notification.permission !== 'denied') {
window.Notification.requestPermission()
} else {
// 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) {
const content = str.substr(1)
if (theme >= 0 && theme < this.themes.length) {
this.palette = this.themes[theme]
if (theme >= 0 && theme < themes.length) {
this.palette = themes[theme]
}
switch (str[0]) {
@ -1500,7 +1456,7 @@ window.TermScreen = class TermScreen extends EventEmitter {
surrOsc.stop(startTime + 0.8)
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
surrGain.gain.value *= 0.8
}

@ -1,5 +1,9 @@
const $ = require('./lib/chibi')
const { qs } = require('./utils')
const modal = require('./modal')
/** File upload utility */
window.TermUpl = function (conn, input, screen) {
module.exports = function (conn, input, screen) {
let lines, // array of lines without newlines
line_i, // current line index
fuTout, // timeout handle for line sending
@ -14,7 +18,7 @@ window.TermUpl = function (conn, input, screen) {
function openUploadDialog () {
updateStatus('Ready...')
Modal.show('#fu_modal', onDialogClose)
modal.show('#fu_modal', onDialogClose)
$('#fu_form').toggleClass('busy', false)
input.blockKeys(true)
}
@ -125,19 +129,19 @@ window.TermUpl = function (conn, input, screen) {
}
function fuClose () {
Modal.hide('#fu_modal')
modal.hide('#fu_modal')
}
return {
init: function () {
qs('#fu_file').addEventListener('change', function (evt) {
let reader = new FileReader()
let reader = new window.FileReader()
let file = evt.target.files[0]
let ftype = file.type || 'application/octet-stream'
console.log('Selected file type: ' + ftype)
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
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 = ''
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 */
function mk (e) {
exports.mk = function mk (e) {
return document.createElement(e)
}
/** Find one by query */
function qs (s) {
exports.qs = function qs (s) {
return document.querySelector(s)
}
/** Find all by query */
function qsa (s) {
exports.qsa = function qsa (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,
* and when they're pressed, fire the callback.
* use $(...).on('keypress', cr(handler))
*/
function cr (hdl) {
exports.cr = function cr (hdl) {
return function (e) {
if (e.which === 10 || e.which === 13 || e.which === 32) {
hdl()
@ -31,53 +26,33 @@ function cr (hdl) {
}
}
/** HTML escape */
function esc (str) {
return $.htmlEscape(str)
}
/** 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)
/** Convert any to bool safely */
exports.bool = function bool (x) {
return (x === 1 || x === '1' || x === true || x === 'true')
}
/** 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
}
/** 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
}
/** Encode using 2B encoding, returns string. */
function encode2B (n) {
exports.encode2B = function encode2B (n) {
let lsb, msb
lsb = (n % 127)
n = ((n - lsb) / 127)
lsb += 1
msb = (n + 1)
return Chr(lsb) + Chr(msb)
return String.fromCharCode(lsb) + String.fromCharCode(msb)
}
/** Encode using 3B encoding, returns string. */
function encode3B (n) {
exports.encode3B = function encode3B (n) {
let lsb, msb, xsb
lsb = (n % 127)
n = (n - lsb) / 127
@ -86,5 +61,5 @@ function encode3B (n) {
n = (n - msb) / 127
msb += 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']
let curSSID
@ -15,8 +19,8 @@
$('#sta-nw').toggleClass('hidden', name.length === 0)
$('#sta-nw-nil').toggleClass('hidden', name.length > 0)
$('#sta-nw .essid').html(esc(name))
const nopw = undef(password) || password.length === 0
$('#sta-nw .essid').html($.htmlEscape(name))
const nopw = !password || password.length === 0
$('#sta-nw .passwd').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'))
@ -96,7 +100,7 @@
if (+$th.data('pwd')) {
// 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
}
@ -120,10 +124,10 @@
/** Ask the CGI what APs are visible (async) */
function scanAPs () {
if (_demo) {
onScan(_demo_aps, 200)
if (window._demo) {
onScan(window._demo_aps, 200)
} else {
$.get('http://' + _root + '/cfg/wifi/scan', onScan)
$.get('http://' + window._root + '/cfg/wifi/scan', onScan)
}
}

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

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

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