cleaning and jslint syntax fixes
This commit is contained in:
+266
-277
@@ -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) : ' ';
|
button.innerHTML = label ? e(label) : ' ';
|
||||||
@@ -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
Submodule libesphttpd updated: 3479ab3efc...13fa224963
Reference in New Issue
Block a user