cleaning and jslint syntax fixes

http-comm
Ondřej Hruška 7 years ago
parent 8f81616ca8
commit fe44927a62
  1. 553
      html_orig/jssrc/term_screen.js
  2. 2
      libesphttpd

@ -5,60 +5,48 @@ const frakturExceptions = {
'I': '\u2111', 'I': '\u2111',
'R': '\u211c', 'R': '\u211c',
'Z': '\u2128' 'Z': '\u2128'
} };
// constants for decoding the update blob // constants for decoding the update blob
const SEQ_SET_COLOR_ATTR = 1 const SEQ_SET_COLOR_ATTR = 1;
const SEQ_REPEAT = 2 const SEQ_REPEAT = 2;
const SEQ_SET_COLOR = 3 const SEQ_SET_COLOR = 3;
const SEQ_SET_ATTR = 4 const SEQ_SET_ATTR = 4;
const SELECTION_BG = '#b2d7fe' const SELECTION_BG = '#b2d7fe';
const SELECTION_FG = '#333' const SELECTION_FG = '#333';
const themes = [ const themes = [
[ // Tango [ // Tango
'#111213', '#CC0000', '#4E9A06', '#C4A000', '#3465A4', '#75507B', '#06989A', '#111213', '#CC0000', '#4E9A06', '#C4A000', '#3465A4', '#75507B', '#06989A', '#D3D7CF',
'#D3D7CF', '#555753', '#EF2929', '#8AE234', '#FCE94F', '#729FCF', '#AD7FA8', '#34E2E2', '#EEEEEC',
'#555753', '#EF2929', '#8AE234', '#FCE94F', '#729FCF', '#AD7FA8', '#34E2E2',
'#EEEEEC'
], ],
[ // Linux [ // Linux
'#000000', '#aa0000', '#00aa00', '#aa5500', '#0000aa', '#aa00aa', '#00aaaa', '#000000', '#aa0000', '#00aa00', '#aa5500', '#0000aa', '#aa00aa', '#00aaaa', '#aaaaaa',
'#aaaaaa', '#555555', '#ff5555', '#55ff55', '#ffff55', '#5555ff', '#ff55ff', '#55ffff', '#ffffff',
'#555555', '#ff5555', '#55ff55', '#ffff55', '#5555ff', '#ff55ff', '#55ffff',
'#ffffff'
], ],
[ // xterm [ // xterm
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000ee', '#cd00cd', '#00cdcd', '#e5e5e5',
'#e5e5e5', '#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff', '#ffffff',
'#7f7f7f', '#ff0000', '#00ff00', '#ffff00', '#5c5cff', '#ff00ff', '#00ffff',
'#ffffff'
], ],
[ // rxvt [ // rxvt
'#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000cd', '#cd00cd', '#00cdcd', '#000000', '#cd0000', '#00cd00', '#cdcd00', '#0000cd', '#cd00cd', '#00cdcd', '#faebd7',
'#faebd7', '#404040', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff',
'#404040', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff',
'#ffffff'
], ],
[ // Ambience [ // Ambience
'#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#2e3436', '#cc0000', '#4e9a06', '#c4a000', '#3465a4', '#75507b', '#06989a', '#d3d7cf',
'#d3d7cf', '#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2', '#eeeeec',
'#555753', '#ef2929', '#8ae234', '#fce94f', '#729fcf', '#ad7fa8', '#34e2e2',
'#eeeeec'
], ],
[ // Solarized [ // Solarized
'#073642', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#073642', '#dc322f', '#859900', '#b58900', '#268bd2', '#d33682', '#2aa198', '#eee8d5',
'#eee8d5', '#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1', '#fdf6e3',
'#002b36', '#cb4b16', '#586e75', '#657b83', '#839496', '#6c71c4', '#93a1a1',
'#fdf6e3'
] ]
] ];
class TermScreen { class TermScreen {
constructor () { constructor () {
this.canvas = document.createElement('canvas') this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d') this.ctx = this.canvas.getContext('2d');
if ('AudioContext' in window || 'webkitAudioContext' in window) { if ('AudioContext' in window || 'webkitAudioContext' in window) {
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)() this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
@ -76,23 +64,22 @@ class TermScreen {
visible: true, visible: true,
hanging: false, hanging: false,
style: 'block', style: 'block',
blinkInterval: null blinkInterval: 0,
} };
this._colors = themes[0] this._colors = themes[0];
this._window = { this._window = {
width: 0, width: 0,
height: 0, height: 0,
devicePixelRatio: 1, devicePixelRatio: 1,
fontFamily: '"DejaVu Sans Mono", "Liberation Mono", "Inconsolata", ' + fontFamily: '"DejaVu Sans Mono", "Liberation Mono", "Inconsolata", monospace',
'monospace',
fontSize: 20, fontSize: 20,
gridScaleX: 1.0, gridScaleX: 1.0,
gridScaleY: 1.2, gridScaleY: 1.2,
blinkStyleOn: true, blinkStyleOn: true,
blinkInterval: null blinkInterval: null,
} };
this.windowState = { this.windowState = {
width: 0, width: 0,
height: 0, height: 0,
@ -100,110 +87,109 @@ class TermScreen {
gridScaleX: 0, gridScaleX: 0,
gridScaleY: 0, gridScaleY: 0,
fontFamily: '', fontFamily: '',
fontSize: 0 fontSize: 0,
} };
this.selection = { this.selection = {
selectable: true, selectable: true,
start: [0, 0], start: [0, 0],
end: [0, 0] end: [0, 0],
} };
this.mouseMode = { clicks: false, movement: false } this.mouseMode = { clicks: false, movement: false };
const self = this const self = this;
this.window = new Proxy(this._window, { this.window = new Proxy(this._window, {
set (target, key, value, receiver) { set (target, key, value, receiver) {
target[key] = value target[key] = value;
self.updateSize() self.updateSize();
self.scheduleDraw() self.scheduleDraw();
return true return true
} }
}) });
this.screen = [] this.screen = [];
this.screenFG = [] this.screenFG = [];
this.screenBG = [] this.screenBG = [];
this.screenAttrs = [] this.screenAttrs = [];
this.resetBlink() this.resetBlink();
this.resetCursorBlink() this.resetCursorBlink();
let selecting = false let selecting = false;
this.canvas.addEventListener('mousedown', e => { this.canvas.addEventListener('mousedown', e => {
if (this.selection.selectable || e.altKey) { if (this.selection.selectable || e.altKey) {
let x = e.offsetX let x = e.offsetX;
let y = e.offsetY let y = e.offsetY;
selecting = true selecting = true;
this.selection.start = this.selection.end = this.screenToGrid(x, y) this.selection.start = this.selection.end = this.screenToGrid(x, y);
this.scheduleDraw() this.scheduleDraw()
} else { } else {
Input.onMouseDown(...this.screenToGrid(e.offsetX, e.offsetY), Input.onMouseDown(...this.screenToGrid(e.offsetX, e.offsetY),
e.button + 1) e.button + 1)
} }
}) });
window.addEventListener('mousemove', e => { window.addEventListener('mousemove', e => {
if (selecting) { if (selecting) {
this.selection.end = this.screenToGrid(e.offsetX, e.offsetY) this.selection.end = this.screenToGrid(e.offsetX, e.offsetY);
this.scheduleDraw() this.scheduleDraw()
} }
}) });
window.addEventListener('mouseup', e => { window.addEventListener('mouseup', e => {
if (selecting) { if (selecting) {
selecting = false selecting = false;
this.selection.end = this.screenToGrid(e.offsetX, e.offsetY) this.selection.end = this.screenToGrid(e.offsetX, e.offsetY);
this.scheduleDraw() this.scheduleDraw();
Object.assign(this.selection, this.getNormalizedSelection()) Object.assign(this.selection, this.getNormalizedSelection())
} }
}) });
this.canvas.addEventListener('mousemove', e => { this.canvas.addEventListener('mousemove', e => {
if (!selecting) { if (!selecting) {
Input.onMouseMove(...this.screenToGrid(e.offsetX, e.offsetY)) Input.onMouseMove(...this.screenToGrid(e.offsetX, e.offsetY))
} }
}) });
this.canvas.addEventListener('mouseup', e => { this.canvas.addEventListener('mouseup', e => {
if (!selecting) { if (!selecting) {
Input.onMouseUp(...this.screenToGrid(e.offsetX, e.offsetY), Input.onMouseUp(...this.screenToGrid(e.offsetX, e.offsetY),
e.button + 1) e.button + 1)
} }
}) });
this.canvas.addEventListener('wheel', e => { this.canvas.addEventListener('wheel', e => {
if (this.mouseMode.clicks) { if (this.mouseMode.clicks) {
Input.onMouseWheel(...this.screenToGrid(e.offsetX, e.offsetY), Input.onMouseWheel(...this.screenToGrid(e.offsetX, e.offsetY),
e.deltaY > 0 ? 1 : -1) e.deltaY > 0 ? 1 : -1);
// prevent page scrolling // prevent page scrolling
e.preventDefault() e.preventDefault()
} }
}) });
// bind ctrl+shift+c to copy // bind ctrl+shift+c to copy
key('⌃+⇧+c', e => { key('⌃+⇧+c', e => {
e.preventDefault() e.preventDefault();
this.copySelectionToClipboard() this.copySelectionToClipboard()
}) })
} }
get colors () { return this._colors } get colors () { return this._colors }
set colors (theme) { set colors (theme) {
this._colors = theme this._colors = theme;
this.scheduleDraw() this.scheduleDraw()
} }
// schedule a draw in the next tick // schedule a draw in the next tick
scheduleDraw () { scheduleDraw () {
clearTimeout(this._scheduledDraw) clearTimeout(this._scheduledDraw);
this._scheduledDraw = setTimeout(() => this.draw(), 1) this._scheduledDraw = setTimeout(() => this.draw(), 1)
} }
getFont (modifiers = {}) { getFont (modifiers = {}) {
let fontStyle = modifiers.style || 'normal' let fontStyle = modifiers.style || 'normal';
let fontWeight = modifiers.weight || 'normal' let fontWeight = modifiers.weight || 'normal';
return `${fontStyle} normal ${fontWeight} ${ return `${fontStyle} normal ${fontWeight} ${this.window.fontSize}px ${this.window.fontFamily}`
this.window.fontSize}px ${this.window.fontFamily}`
} }
getCharSize () { getCharSize () {
this.ctx.font = this.getFont() this.ctx.font = this.getFont();
return { return {
width: this.ctx.measureText(' ').width, width: this.ctx.measureText(' ').width,
@ -212,59 +198,59 @@ class TermScreen {
} }
updateSize () { updateSize () {
this._window.devicePixelRatio = window.devicePixelRatio || 1 this._window.devicePixelRatio = window.devicePixelRatio || 1;
let didChange = false let didChange = false;
for (let key in this.windowState) { for (let key in this.windowState) {
if (this.windowState[key] !== this.window[key]) { if (this.windowState.hasOwnProperty(key) && this.windowState[key] !== this.window[key]) {
didChange = true didChange = true;
this.windowState[key] = this.window[key] this.windowState[key] = this.window[key]
} }
} }
if (didChange) { if (didChange) {
const { const {
width, height, devicePixelRatio, gridScaleX, gridScaleY, fontSize width, height, devicePixelRatio, gridScaleX, gridScaleY
} = this.window } = this.window;
const charSize = this.getCharSize() const charSize = this.getCharSize();
this.canvas.width = width * devicePixelRatio * charSize.width * gridScaleX this.canvas.width = width * devicePixelRatio * charSize.width * gridScaleX;
this.canvas.style.width = `${Math.ceil(width * charSize.width * this.canvas.style.width = `${Math.ceil(width * charSize.width *
gridScaleX)}px` gridScaleX)}px`;
this.canvas.height = height * devicePixelRatio * charSize.height * this.canvas.height = height * devicePixelRatio * charSize.height *
gridScaleY gridScaleY;
this.canvas.style.height = `${Math.ceil(height * charSize.height * this.canvas.style.height = `${Math.ceil(height * charSize.height *
gridScaleY)}px` gridScaleY)}px`
} }
} }
resetCursorBlink () { resetCursorBlink () {
this.cursor.blinkOn = true this.cursor.blinkOn = true;
clearInterval(this.cursor.blinkInterval) clearInterval(this.cursor.blinkInterval);
this.cursor.blinkInterval = setInterval(() => { this.cursor.blinkInterval = setInterval(() => {
this.cursor.blinkOn = !this.cursor.blinkOn this.cursor.blinkOn = !this.cursor.blinkOn;
this.scheduleDraw() this.scheduleDraw()
}, 500) }, 500)
} }
resetBlink () { resetBlink () {
this.window.blinkStyleOn = true this.window.blinkStyleOn = true;
clearInterval(this.window.blinkInterval) clearInterval(this.window.blinkInterval);
let intervals = 0 let intervals = 0;
this.window.blinkInterval = setInterval(() => { this.window.blinkInterval = setInterval(() => {
intervals++ intervals++;
if (intervals >= 4 && this.window.blinkStyleOn) { if (intervals >= 4 && this.window.blinkStyleOn) {
this.window.blinkStyleOn = false this.window.blinkStyleOn = false;
intervals = 0 intervals = 0
} else if (intervals >= 1 && !this.window.blinkStyleOn) { } else if (intervals >= 1 && !this.window.blinkStyleOn) {
this.window.blinkStyleOn = true this.window.blinkStyleOn = true;
intervals = 0 intervals = 0
} }
}, 200) }, 200)
} }
getNormalizedSelection () { getNormalizedSelection () {
let { start, end } = this.selection let { start, end } = this.selection;
// if the start line is after the end line, or if they're both on the same // if the start line is after the end line, or if they're both on the same
// line but the start column comes after the end column, swap // line but the start column comes after the end column, swap
if (start[1] > end[1] || (start[1] === end[1] && start[0] > end[0])) { if (start[1] > end[1] || (start[1] === end[1] && start[0] > end[0])) {
@ -274,30 +260,30 @@ class TermScreen {
} }
isInSelection (col, line) { isInSelection (col, line) {
let { start, end } = this.getNormalizedSelection() let { start, end } = this.getNormalizedSelection();
let colAfterStart = start[0] <= col let colAfterStart = start[0] <= col;
let colBeforeEnd = col < end[0] let colBeforeEnd = col < end[0];
let onStartLine = line === start[1] let onStartLine = line === start[1];
let onEndLine = line === end[1] let onEndLine = line === end[1];
if (onStartLine && onEndLine) return colAfterStart && colBeforeEnd if (onStartLine && onEndLine) return colAfterStart && colBeforeEnd;
else if (onStartLine) return colAfterStart else if (onStartLine) return colAfterStart;
else if (onEndLine) return colBeforeEnd else if (onEndLine) return colBeforeEnd;
else return start[1] < line && line < end[1] else return start[1] < line && line < end[1]
} }
getSelectedText () { getSelectedText () {
const screenLength = this.window.width * this.window.height const screenLength = this.window.width * this.window.height;
let lines = [] let lines = [];
let previousLineIndex = -1 let previousLineIndex = -1;
for (let cell = 0; cell < screenLength; cell++) { for (let cell = 0; cell < screenLength; cell++) {
let x = cell % this.window.width let x = cell % this.window.width;
let y = Math.floor(cell / this.window.width) let y = Math.floor(cell / this.window.width);
if (this.isInSelection(x, y)) { if (this.isInSelection(x, y)) {
if (previousLineIndex !== y) { if (previousLineIndex !== y) {
previousLineIndex = y previousLineIndex = y;
lines.push('') lines.push('')
} }
lines[lines.length - 1] += this.screen[cell] lines[lines.length - 1] += this.screen[cell]
@ -308,71 +294,71 @@ class TermScreen {
} }
copySelectionToClipboard () { copySelectionToClipboard () {
let selectedText = this.getSelectedText() let selectedText = this.getSelectedText();
// don't copy anything if nothing is selected // don't copy anything if nothing is selected
if (!selectedText) return if (!selectedText) return;
let textarea = document.createElement('textarea') let textarea = document.createElement('textarea');
document.body.appendChild(textarea) document.body.appendChild(textarea);
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 {
console.warn('Copy failed');
// unsuccessful copy // unsuccessful copy
// TODO: do something?
} }
document.body.removeChild(textarea) document.body.removeChild(textarea);
} }
screenToGrid (x, y) { screenToGrid (x, y) {
let charSize = this.getCharSize() let charSize = this.getCharSize();
let cellWidth = charSize.width * this.window.gridScaleX let cellWidth = charSize.width * this.window.gridScaleX;
let cellHeight = charSize.height * this.window.gridScaleY let cellHeight = charSize.height * this.window.gridScaleY;
return [ return [
Math.floor((x + cellWidth / 2) / cellWidth), Math.floor((x + cellWidth / 2) / cellWidth),
Math.floor(y / cellHeight) Math.floor(y / cellHeight)
] ];
} }
drawCell ({ x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs }, drawCell ({ x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs },
compositeAbove = false) { compositeAbove = false) {
const ctx = this.ctx const ctx = this.ctx;
const inSelection = this.isInSelection(x, y) const inSelection = this.isInSelection(x, y);
ctx.fillStyle = inSelection ? SELECTION_BG : this.colors[bg] ctx.fillStyle = inSelection ? SELECTION_BG : this.colors[bg];
if (!compositeAbove) ctx.globalCompositeOperation = 'destination-over' if (!compositeAbove) ctx.globalCompositeOperation = 'destination-over';
ctx.fillRect(x * cellWidth, y * cellHeight, ctx.fillRect(x * cellWidth, y * cellHeight,
Math.ceil(cellWidth), Math.ceil(cellHeight)) Math.ceil(cellWidth), Math.ceil(cellHeight));
ctx.globalCompositeOperation = 'source-over' ctx.globalCompositeOperation = 'source-over';
let fontModifiers = {} let fontModifiers = {};
let underline = false let underline = false;
let blink = false let blink = false;
let strike = false let strike = false;
if (attrs & 1) fontModifiers.weight = 'bold' if (attrs & 1) fontModifiers.weight = 'bold';
if (attrs & 1 << 1) ctx.globalAlpha = 0.5 if (attrs & 1 << 1) ctx.globalAlpha = 0.5;
if (attrs & 1 << 2) fontModifiers.style = 'italic' if (attrs & 1 << 2) fontModifiers.style = 'italic';
if (attrs & 1 << 3) underline = true if (attrs & 1 << 3) underline = true;
if (attrs & 1 << 4) blink = true if (attrs & 1 << 4) blink = true;
if (attrs & 1 << 5) text = TermScreen.alphaToFraktur(text) if (attrs & 1 << 5) text = TermScreen.alphaToFraktur(text);
if (attrs & 1 << 6) strike = true if (attrs & 1 << 6) strike = true;
if (!blink || this.window.blinkStyleOn) { if (!blink || this.window.blinkStyleOn) {
ctx.font = this.getFont(fontModifiers) ctx.font = this.getFont(fontModifiers);
ctx.fillStyle = inSelection ? SELECTION_FG : this.colors[fg] ctx.fillStyle = inSelection ? SELECTION_FG : this.colors[fg];
ctx.fillText(text, (x + 0.5) * cellWidth, (y + 0.5) * cellHeight) ctx.fillText(text, (x + 0.5) * cellWidth, (y + 0.5) * cellHeight);
if (underline || strike) { if (underline || strike) {
let lineY = underline let lineY = underline
? y * cellHeight + charSize.height ? y * cellHeight + charSize.height
: (y + 0.5) * cellHeight : (y + 0.5) * cellHeight;
ctx.strokeStyle = inSelection ? SELECTION_FG : this.colors[fg] ctx.strokeStyle = inSelection ? SELECTION_FG : this.colors[fg];
ctx.lineWidth = this.window.fontSize / 10 ctx.lineWidth = this.window.fontSize / 10;
ctx.lineCap = 'round' ctx.lineCap = 'round';
ctx.beginPath() ctx.beginPath();
ctx.moveTo(x * cellWidth, lineY) ctx.moveTo(x * cellWidth, lineY);
ctx.lineTo((x + 1) * cellWidth, lineY) ctx.lineTo((x + 1) * cellWidth, lineY);
ctx.stroke() ctx.stroke()
} }
} }
@ -381,76 +367,73 @@ class TermScreen {
} }
draw () { draw () {
const ctx = this.ctx const ctx = this.ctx;
const { const {
width, width,
height, height,
devicePixelRatio, devicePixelRatio,
fontFamily,
fontSize,
gridScaleX, gridScaleX,
gridScaleY gridScaleY
} = this.window } = this.window;
const charSize = this.getCharSize() const charSize = this.getCharSize();
const cellWidth = charSize.width * gridScaleX const cellWidth = charSize.width * gridScaleX;
const cellHeight = charSize.height * gridScaleY const cellHeight = charSize.height * gridScaleY;
const screenWidth = width * cellWidth const screenWidth = width * cellWidth;
const screenHeight = height * cellHeight const screenHeight = height * cellHeight;
const screenLength = width * height const screenLength = width * height;
ctx.setTransform(this.window.devicePixelRatio, 0, 0, ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
this.window.devicePixelRatio, 0, 0) ctx.clearRect(0, 0, screenWidth, screenHeight);
ctx.clearRect(0, 0, screenWidth, screenHeight)
ctx.font = this.getFont() ctx.font = this.getFont();
ctx.textAlign = 'center' ctx.textAlign = 'center';
ctx.textBaseline = 'middle' ctx.textBaseline = 'middle';
for (let cell = 0; cell < screenLength; cell++) { for (let cell = 0; cell < screenLength; cell++) {
let x = cell % width let x = cell % width;
let y = Math.floor(cell / width) let y = Math.floor(cell / width);
let isCursor = this.cursor.x === x && this.cursor.y === y let isCursor = this.cursor.x === x && this.cursor.y === y;
if (this.cursor.hanging) isCursor = false if (this.cursor.hanging) isCursor = false;
let invertForCursor = isCursor && this.cursor.blinkOn && let invertForCursor = isCursor && this.cursor.blinkOn &&
this.cursor.style === 'block' this.cursor.style === 'block';
let text = this.screen[cell] let text = this.screen[cell];
let fg = invertForCursor ? this.screenBG[cell] : this.screenFG[cell] let fg = invertForCursor ? this.screenBG[cell] : this.screenFG[cell];
let bg = invertForCursor ? this.screenFG[cell] : this.screenBG[cell] let bg = invertForCursor ? this.screenFG[cell] : this.screenBG[cell];
let attrs = this.screenAttrs[cell] let attrs = this.screenAttrs[cell];
// HACK: ensure cursor is visible // HACK: ensure cursor is visible
if (invertForCursor && fg === bg) bg = fg === 0 ? 7 : 0 if (invertForCursor && fg === bg) bg = fg === 0 ? 7 : 0;
this.drawCell({ this.drawCell({
x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs
}) });
if (isCursor && this.cursor.blinkOn && this.cursor.style !== 'block') { if (isCursor && this.cursor.blinkOn && this.cursor.style !== 'block') {
ctx.save() ctx.save();
ctx.beginPath() ctx.beginPath();
if (this.cursor.style === 'bar') { if (this.cursor.style === 'bar') {
// vertical bar // vertical bar
let barWidth = 2 let barWidth = 2;
ctx.rect(x * cellWidth, y * cellHeight, barWidth, cellHeight) ctx.rect(x * cellWidth, y * cellHeight, barWidth, cellHeight)
} else if (this.cursor.style === 'line') { } else if (this.cursor.style === 'line') {
// underline // underline
let lineHeight = 2 let lineHeight = 2;
ctx.rect(x * cellWidth, y * cellHeight + charSize.height, ctx.rect(x * cellWidth, y * cellHeight + charSize.height,
cellWidth, lineHeight) cellWidth, lineHeight)
} }
ctx.clip() ctx.clip();
// swap foreground/background // swap foreground/background
fg = this.screenBG[cell] fg = this.screenBG[cell];
bg = this.screenFG[cell] bg = this.screenFG[cell];
// HACK: ensure cursor is visible // HACK: ensure cursor is visible
if (fg === bg) bg = fg === 0 ? 7 : 0 if (fg === bg) bg = fg === 0 ? 7 : 0;
this.drawCell({ this.drawCell({
x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs x, y, charSize, cellWidth, cellHeight, text, fg, bg, attrs
}, true) }, true);
ctx.restore() ctx.restore()
} }
} }
@ -458,115 +441,119 @@ class TermScreen {
loadContent (str) { loadContent (str) {
// current index // current index
let i = 0 let i = 0;
// window size // window size
this.window.height = parse2B(str, i) this.window.height = parse2B(str, i);
this.window.width = parse2B(str, i + 2) this.window.width = parse2B(str, i + 2);
this.updateSize() this.updateSize();
i += 4 i += 4;
// cursor position // cursor position
let [cursorY, cursorX] = [parse2B(str, i), parse2B(str, i + 2)] let [cursorY, cursorX] = [parse2B(str, i), parse2B(str, i + 2)];
i += 4 i += 4;
let cursorMoved = (cursorX !== this.cursor.x || cursorY !== this.cursor.y) let cursorMoved = (cursorX !== this.cursor.x || cursorY !== this.cursor.y);
this.cursor.x = cursorX this.cursor.x = cursorX;
this.cursor.y = cursorY this.cursor.y = cursorY;
if (cursorMoved) this.resetCursorBlink() if (cursorMoved) this.resetCursorBlink();
// attributes // attributes
let attributes = parse2B(str, i) let attributes = parse2B(str, i);
i += 2 i += 2;
this.cursor.visible = !!(attributes & 1) this.cursor.visible = !!(attributes & 1);
this.cursor.hanging = !!(attributes & 1 << 1) this.cursor.hanging = !!(attributes & 1 << 1);
Input.setAlts( Input.setAlts(
!!(attributes & 1 << 2), // cursors alt !!(attributes & 1 << 2), // cursors alt
!!(attributes & 1 << 3), // numpad alt !!(attributes & 1 << 3), // numpad alt
!!(attributes & 1 << 4) // fn keys alt !!(attributes & 1 << 4) // fn keys alt
) );
let trackMouseClicks = !!(attributes & 1 << 5) let trackMouseClicks = !!(attributes & 1 << 5);
let trackMouseMovement = !!(attributes & 1 << 6) let trackMouseMovement = !!(attributes & 1 << 6);
Input.setMouseMode(trackMouseClicks, trackMouseMovement) Input.setMouseMode(trackMouseClicks, trackMouseMovement);
this.selection.selectable = !trackMouseMovement this.selection.selectable = !trackMouseMovement;
this.mouseMode = { this.mouseMode = {
clicks: trackMouseClicks, clicks: trackMouseClicks,
movement: trackMouseMovement movement: trackMouseMovement
} };
let showButtons = !!(attributes & 1 << 7) let showButtons = !!(attributes & 1 << 7);
let showConfigLinks = !!(attributes & 1 << 8) let showConfigLinks = !!(attributes & 1 << 8);
$('.x-term-conf-btn').toggleClass('hidden', !showConfigLinks) $('.x-term-conf-btn').toggleClass('hidden', !showConfigLinks);
$('#action-buttons').toggleClass('hidden', !showButtons) $('#action-buttons').toggleClass('hidden', !showButtons);
// content // content
let fg = 7 let fg = 7;
let bg = 0 let bg = 0;
let attrs = 0 let attrs = 0;
let cell = 0 // cell index let cell = 0; // cell index
let text = ' ' let text = ' ';
let screenLength = this.window.width * this.window.height let screenLength = this.window.width * this.window.height;
this.screen = new Array(screenLength).fill(' ') this.screen = new Array(screenLength).fill(' ');
this.screenFG = new Array(screenLength).fill(' ') this.screenFG = new Array(screenLength).fill(' ');
this.screenBG = new Array(screenLength).fill(' ') this.screenBG = new Array(screenLength).fill(' ');
this.screenAttrs = new Array(screenLength).fill(' ') this.screenAttrs = new Array(screenLength).fill(' ');
while (i < str.length && cell < screenLength) { while (i < str.length && cell < screenLength) {
let character = str[i++] let character = str[i++];
let charCode = character.charCodeAt(0) let charCode = character.charCodeAt(0);
if (charCode === SEQ_SET_COLOR_ATTR) { if (charCode === SEQ_SET_COLOR_ATTR) {
let data = parse3B(str, i) let data = parse3B(str, i);
i += 3 i += 3;
fg = data & 0xF fg = data & 0xF;
bg = data >> 4 & 0xF bg = data >> 4 & 0xF;
attrs = data >> 8 & 0xFF attrs = data >> 8 & 0xFF
} else if (charCode == SEQ_SET_COLOR) { }
let data = parse2B(str, i) else if (charCode == SEQ_SET_COLOR) {
i += 2 let data = parse2B(str, i);
fg = data & 0xF i += 2;
fg = data & 0xF;
bg = data >> 4 & 0xF bg = data >> 4 & 0xF
} else if (charCode === SEQ_SET_ATTR) { }
let data = parse2B(str, i) else if (charCode === SEQ_SET_ATTR) {
i += 2 let data = parse2B(str, i);
i += 2;
attrs = data & 0xFF attrs = data & 0xFF
} else if (charCode === SEQ_REPEAT) { }
let count = parse2B(str, i) else if (charCode === SEQ_REPEAT) {
i += 2 let count = parse2B(str, i);
i += 2;
for (let j = 0; j < count; j++) { for (let j = 0; j < count; j++) {
this.screen[cell] = text this.screen[cell] = text;
this.screenFG[cell] = fg this.screenFG[cell] = fg;
this.screenBG[cell] = bg this.screenBG[cell] = bg;
this.screenAttrs[cell] = attrs this.screenAttrs[cell] = attrs;
if (++cell > screenLength) break if (++cell > screenLength) break
} }
} else { }
else {
// unique cell character // unique cell character
this.screen[cell] = text = character this.screen[cell] = text = character;
this.screenFG[cell] = fg this.screenFG[cell] = fg;
this.screenBG[cell] = bg this.screenBG[cell] = bg;
this.screenAttrs[cell] = attrs this.screenAttrs[cell] = attrs;
cell++ cell++
} }
} }
this.scheduleDraw() this.scheduleDraw();
if (this.onload) this.onload() if (this.onload) this.onload()
} }
/** Apply labels to buttons and screen title (leading T removed already) */ /** Apply labels to buttons and screen title (leading T removed already) */
loadLabels (str) { loadLabels (str) {
let pieces = str.split('\x01') let pieces = str.split('\x01');
qs('h1').textContent = pieces[0] qs('h1').textContent = pieces[0];
$('#action-buttons button').forEach((button, i) => { $('#action-buttons button').forEach((button, i) => {
var 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 ? e(label) : '&nbsp;'; button.innerHTML = label ? e(label) : '&nbsp;';
@ -575,34 +562,33 @@ class TermScreen {
} }
load (str) { load (str) {
const content = str.substr(1) const content = str.substr(1);
switch (str[0]) { switch (str[0]) {
case 'S': case 'S':
this.loadContent(content) this.loadContent(content);
break break;
case 'T': case 'T':
this.loadLabels(content) this.loadLabels(content);
break break;
case 'B': case 'B':
this.beep() this.beep();
break break;
default: default:
console.warn(`Bad data message type; ignoring.\n${ console.warn(`Bad data message type; ignoring.\n${JSON.stringify(content)}`)
JSON.stringify(content)}`)
} }
} }
beep () { beep () {
const audioCtx = this.audioCtx const audioCtx = this.audioCtx;
if (!audioCtx) return if (!audioCtx) return;
let osc, gain let osc, gain;
// main beep // main beep
osc = audioCtx.createOscillator() osc = audioCtx.createOscillator();
gain = audioCtx.createGain() gain = audioCtx.createGain();
osc.connect(gain) osc.connect(gain);
gain.connect(audioCtx.destination); gain.connect(audioCtx.destination);
gain.gain.value = 0.5; gain.gain.value = 0.5;
osc.frequency.value = 750; osc.frequency.value = 750;
@ -633,13 +619,16 @@ class TermScreen {
} }
} }
const Screen = new TermScreen() const Screen = new TermScreen();
let didAddScreen = false
let didAddScreen = false;
Screen.onload = function () { Screen.onload = function () {
if (didAddScreen) return if (didAddScreen) return;
didAddScreen = true didAddScreen = true;
qs('#screen').appendChild(Screen.canvas) qs('#screen').appendChild(Screen.canvas);
for (let item of qs('#screen').classList) { for (let item of qs('#screen').classList) {
if (item.startsWith('theme-')) Screen.colors = themes[item.substr(6)] if (item.startsWith('theme-')) {
Screen.colors = themes[item.substr(6)]
}
} }
} };

@ -1 +1 @@
Subproject commit 3479ab3efcb4581669370cde6a607f936ff5515a Subproject commit 13fa224963081e9ff298abd74a59faafcb9bf816
Loading…
Cancel
Save