Browse Source

run formatting on client config (#359)

pull/358/head
Cian Butler 3 years ago
committed by GitHub
parent
commit
ac3c49289c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 236
      src/assets/xterm_config/functionality.js
  2. 132
      src/assets/xterm_config/index.html
  3. 103
      src/assets/xterm_config/style.css
  4. 245
      src/assets/xterm_config/xterm_advanced_options.js
  5. 280
      src/assets/xterm_config/xterm_color_theme.js
  6. 129
      src/assets/xterm_config/xterm_defaults.js
  7. 239
      src/assets/xterm_config/xterm_general_options.js
  8. 4
      src/buffer.ts
  9. 4
      src/client/dev.ts
  10. 33
      src/client/wetty/term/confiruragtion.ts
  11. 15
      src/client/wetty/term/confiruragtion/editor.ts
  12. 7
      src/main.ts
  13. 10
      src/server/socketServer/html.ts
  14. 2
      src/server/spawn.ts

236
src/assets/xterm_config/functionality.js

@ -1,126 +1,162 @@
function optionGenericGet() { return this.el.querySelector("input").value; } function optionGenericGet() {
function optionGenericSet(value) { this.el.querySelector("input").value = value; } return this.el.querySelector('input').value;
function optionEnumGet() { return this.el.querySelector("select").value; } }
function optionEnumSet(value) { this.el.querySelector("select").value = value; } function optionGenericSet(value) {
function optionBoolGet() { return this.el.querySelector("input").checked; } this.el.querySelector('input').value = value;
function optionBoolSet(value) { this.el.querySelector("input").checked = value; } }
function optionEnumGet() {
return this.el.querySelector('select').value;
}
function optionEnumSet(value) {
this.el.querySelector('select').value = value;
}
function optionBoolGet() {
return this.el.querySelector('input').checked;
}
function optionBoolSet(value) {
this.el.querySelector('input').checked = value;
}
function optionNumberGet() { function optionNumberGet() {
let value = (this.float === true ? parseFloat : parseInt)(this.el.querySelector("input").value); let value = (this.float === true ? parseFloat : parseInt)(
if (Number.isNaN(value) || typeof value !== "number") value = 0; this.el.querySelector('input').value,
if (typeof this.min === "number") value = Math.max(value, this.min); );
if (typeof this.max === "number") value = Math.min(value, this.max); if (Number.isNaN(value) || typeof value !== 'number') value = 0;
return value; if (typeof this.min === 'number') value = Math.max(value, this.min);
if (typeof this.max === 'number') value = Math.min(value, this.max);
return value;
}
function optionNumberSet(value) {
this.el.querySelector('input').value = value;
} }
function optionNumberSet(value) { this.el.querySelector("input").value = value; }
const allOptions = []; const allOptions = [];
function inflateOptions(optionsSchema) { function inflateOptions(optionsSchema) {
const booleanOption = document.querySelector("#boolean_option.templ"); const booleanOption = document.querySelector('#boolean_option.templ');
const enumOption = document.querySelector("#enum_option.templ"); const enumOption = document.querySelector('#enum_option.templ');
const textOption = document.querySelector("#text_option.templ"); const textOption = document.querySelector('#text_option.templ');
const numberOption = document.querySelector("#number_option.templ"); const numberOption = document.querySelector('#number_option.templ');
const colorOption = document.querySelector("#color_option.templ"); const colorOption = document.querySelector('#color_option.templ');
function copyOver(element) { function copyOver(element) {
while (element.children.length > 0) document.body.append(element.children[0]); while (element.children.length > 0)
} document.body.append(element.children[0]);
}
optionsSchema.forEach(option => { optionsSchema.forEach(option => {
let el; let el;
option.get = optionGenericGet.bind(option); option.get = optionGenericGet.bind(option);
option.set = optionGenericSet.bind(option); option.set = optionGenericSet.bind(option);
switch (option.type) { switch (option.type) {
case "boolean": case 'boolean':
el = booleanOption.cloneNode(true); el = booleanOption.cloneNode(true);
option.get = optionBoolGet.bind(option); option.get = optionBoolGet.bind(option);
option.set = optionBoolSet.bind(option); option.set = optionBoolSet.bind(option);
break; break;
case "enum": case 'enum':
el = enumOption.cloneNode(true); el = enumOption.cloneNode(true);
option.enum.forEach(varriant => { option.enum.forEach(varriant => {
const optionEl = document.createElement("option"); const optionEl = document.createElement('option');
optionEl.innerText = varriant; optionEl.innerText = varriant;
optionEl.value = varriant; optionEl.value = varriant;
el.querySelector("select").appendChild(optionEl); el.querySelector('select').appendChild(optionEl);
}); });
option.get = optionEnumGet.bind(option); option.get = optionEnumGet.bind(option);
option.set = optionEnumSet.bind(option); option.set = optionEnumSet.bind(option);
break; break;
case "text": case 'text':
el = textOption.cloneNode(true); el = textOption.cloneNode(true);
break; break;
case "number": case 'number':
el = numberOption.cloneNode(true); el = numberOption.cloneNode(true);
if (option.float === true) el.querySelector("input").setAttribute("step", "0.001"); if (option.float === true)
option.get = optionNumberGet.bind(option); el.querySelector('input').setAttribute('step', '0.001');
option.set = optionNumberSet.bind(option); option.get = optionNumberGet.bind(option);
if (typeof option.min === "number") el.querySelector("input").setAttribute("min", option.min.toString()); option.set = optionNumberSet.bind(option);
if (typeof option.max === "number") el.querySelector("input").setAttribute("max", option.max.toString()); if (typeof option.min === 'number')
break; el.querySelector('input').setAttribute('min', option.min.toString());
if (typeof option.max === 'number')
el.querySelector('input').setAttribute('max', option.max.toString());
break;
case "color": case 'color':
el = colorOption.cloneNode(true); el = colorOption.cloneNode(true);
break; break;
default: default:
throw new Error(`Unknown option type ${ option.type}`); throw new Error(`Unknown option type ${option.type}`);
} }
el.querySelector(".title").innerText = option.name; el.querySelector('.title').innerText = option.name;
el.querySelector(".desc").innerText = option.description; el.querySelector('.desc').innerText = option.description;
[ option.el ] = el.children; [option.el] = el.children;
copyOver(el); copyOver(el);
allOptions.push(option); allOptions.push(option);
}); });
} }
function getItem(json, path) { function getItem(json, path) {
const mypath = path[0]; const mypath = path[0];
if (path.length === 1) return json[mypath]; if (path.length === 1) return json[mypath];
if (json[mypath] != null) return getItem(json[mypath], path.slice(1)); if (json[mypath] != null) return getItem(json[mypath], path.slice(1));
return null; return null;
} }
function setItem(json, path, item) { function setItem(json, path, item) {
const mypath = path[0]; const mypath = path[0];
if (path.length === 1) json[mypath] = item; if (path.length === 1) json[mypath] = item;
else { else {
if (json[mypath] == null) json[mypath] = {}; if (json[mypath] == null) json[mypath] = {};
setItem(json[mypath], path.slice(1), item); setItem(json[mypath], path.slice(1), item);
} }
} }
window.loadOptions = function(config) { window.loadOptions = function (config) {
allOptions.forEach(option => { allOptions.forEach(option => {
let value = getItem(config, option.path); let value = getItem(config, option.path);
if (option.nullable === true && option.type === "text" && value == null) value = null; if (option.nullable === true && option.type === 'text' && value == null)
else if (option.nullable === true && option.type === "number" && value == null) value = -1; value = null;
else if (value == null) return; else if (
if (option.json === true && option.type === "text") value = JSON.stringify(value); option.nullable === true &&
option.set(value); option.type === 'number' &&
option.el.classList.remove("unbounded"); value == null
}); )
} value = -1;
else if (value == null) return;
if (option.json === true && option.type === 'text')
value = JSON.stringify(value);
option.set(value);
option.el.classList.remove('unbounded');
});
};
if (window.top === window) alert("Error: Page is top level. This page is supposed to be accessed from inside Wetty."); if (window.top === window)
alert(
'Error: Page is top level. This page is supposed to be accessed from inside Wetty.',
);
function saveConfig() { function saveConfig() {
const newConfig = {}; const newConfig = {};
allOptions.forEach(option => { allOptions.forEach(option => {
let newValue = option.get(); let newValue = option.get();
if (option.nullable === true && ((option.type === "text" && newValue === "") || ( option.type === "number" && newValue < 0))) return; if (
if (option.json === true && option.type === "text") newValue = JSON.parse(newValue); option.nullable === true &&
setItem(newConfig, option.path, newValue); ((option.type === 'text' && newValue === '') ||
}); (option.type === 'number' && newValue < 0))
window.wetty_save_config(newConfig); )
}; return;
if (option.json === true && option.type === 'text')
newValue = JSON.parse(newValue);
setItem(newConfig, option.path, newValue);
});
window.wetty_save_config(newConfig);
}
window.addEventListener("input", () => { window.addEventListener('input', () => {
const els = document.querySelectorAll("input, select"); const els = document.querySelectorAll('input, select');
for (let i = 0; i < els.length; i += 1) { for (let i = 0; i < els.length; i += 1) {
els[i].addEventListener("input", saveConfig); els[i].addEventListener('input', saveConfig);
} }
}); });

132
src/assets/xterm_config/index.html

@ -1,71 +1,71 @@
<!DOCTYPE HTML> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Wetty XTerm Configuration</title> <title>Wetty XTerm Configuration</title>
<link rel="stylesheet" href="./style.css" /> <link rel="stylesheet" href="./style.css" />
</head> </head>
<body> <body>
<header> <header>
<h1>Configure</h1> <h1>Configure</h1>
</header> </header>
<div class="templ" id="boolean_option">
<div class="boolean_option unbounded">
<p>
<b class="title"></b><br>
<span class="desc"></span>
</p>
<input type="checkbox" />
</div>
</div>
<div class="templ" id="enum_option">
<div class="enum_option unbounded">
<p>
<b class="title"></b><br>
<span class="desc"></span>
</p>
<select></select>
</div>
</div>
<div class="templ" id="text_option">
<div class="text_option unbounded">
<p>
<b class="title"></b><br>
<span class="desc"></span>
</p>
<input type="text" />
</div>
<div class="error_reporting"></div>
</div>
<div class="templ" id="number_option">
<div class="number_option unbounded">
<p>
<b class="title"></b><br>
<span class="desc"></span>
</p>
<input type="number" size="10" />
</div>
<div class="error_reporting"></div>
</div>
<div class="templ" id="color_option">
<div class="color_option unbounded">
<p>
<b class="title"></b><br>
<span class="desc"></span>
</p>
<input type="color" />
</div>
</div>
<script src="./functionality.js"></script> <div class="templ" id="boolean_option">
<div class="boolean_option unbounded">
<p>
<b class="title"></b><br />
<span class="desc"></span>
</p>
<input type="checkbox" />
</div>
</div>
<div class="templ" id="enum_option">
<div class="enum_option unbounded">
<p>
<b class="title"></b><br />
<span class="desc"></span>
</p>
<select></select>
</div>
</div>
<div class="templ" id="text_option">
<div class="text_option unbounded">
<p>
<b class="title"></b><br />
<span class="desc"></span>
</p>
<input type="text" />
</div>
<div class="error_reporting"></div>
</div>
<div class="templ" id="number_option">
<div class="number_option unbounded">
<p>
<b class="title"></b><br />
<span class="desc"></span>
</p>
<input type="number" size="10" />
</div>
<div class="error_reporting"></div>
</div>
<div class="templ" id="color_option">
<div class="color_option unbounded">
<p>
<b class="title"></b><br />
<span class="desc"></span>
</p>
<input type="color" />
</div>
</div>
<h2>General Options</h2> <script src="./functionality.js"></script>
<script src="./xterm_general_options.js"></script>
<h2>Color Theme</h2>
<script src="./xterm_color_theme.js"></script>
<h2>Advanced XTerm Options</h2>
<script src="./xterm_advanced_options.js"></script>
<script src="./xterm_defaults.js"></script> <h2>General Options</h2>
</body> <script src="./xterm_general_options.js"></script>
<h2>Color Theme</h2>
<script src="./xterm_color_theme.js"></script>
<h2>Advanced XTerm Options</h2>
<script src="./xterm_advanced_options.js"></script>
<script src="./xterm_defaults.js"></script>
</body>
</html> </html>

103
src/assets/xterm_config/style.css

@ -1,59 +1,78 @@
html { background-color: black; } html {
html, body { overflow: hidden auto; } background-color: black;
}
html,
body {
overflow: hidden auto;
}
body { body {
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
font-family: monospace; font-family: monospace;
font-size: 1rem; font-size: 1rem;
color: white; color: white;
} }
.templ { display: none; } .templ {
h2 { text-align: center; text-decoration: underline; } display: none;
}
h2 {
text-align: center;
text-decoration: underline;
}
header { header {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
align-items: center; align-items: center;
} }
header button { header button {
padding: 0.5em; padding: 0.5em;
font-size: 1em; font-size: 1em;
margin: 0.5em; margin: 0.5em;
border-radius: 0.5em; border-radius: 0.5em;
collapse-margin;
} }
.boolean_option, .number_option, .color_option, .enum_option, .text_option { .boolean_option,
display: grid; .number_option,
grid-template-columns: 100fr min(30em, 50%); .color_option,
grid-template-rows: auto; .enum_option,
align-items: center; .text_option {
} display: grid;
.boolean_option input, .number_option input, .color_option input, .text_option input, .enum_option select { grid-template-columns: 100fr min(30em, 50%);
margin: 0 0.5em; grid-template-rows: auto;
font-size: 1em; align-items: center;
background-color: hsl(0,0%,20%); }
color: white; .boolean_option input,
border: 2px solid white; .number_option input,
.color_option input,
.text_option input,
.enum_option select {
margin: 0 0.5em;
font-size: 1em;
background-color: hsl(0, 0%, 20%);
color: white;
border: 2px solid white;
} }
.number_option input, .text_option input, .enum_option select { .number_option input,
padding: 0.4em; .text_option input,
.enum_option select {
padding: 0.4em;
} }
.boolean_option input { .boolean_option input {
width: 2em; width: 2em;
height: 2em; height: 2em;
font-size: 0.75em; font-size: 0.75em;
justify-self: center; justify-self: center;
} }
.color_option input { .color_option input {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: lightgray; background-color: lightgray;
} }
.unbounded .title::before { .unbounded .title::before {
content: "UNBOUND OPTION "; content: 'UNBOUND OPTION ';
color: red; color: red;
font-weight: bold; font-weight: bold;
} }

245
src/assets/xterm_config/xterm_advanced_options.js

@ -1,122 +1,127 @@
window.inflateOptions([ window.inflateOptions([
{ {
type: "boolean", type: 'boolean',
name: "Allow Proposed XTerm APIs", name: 'Allow Proposed XTerm APIs',
description: "When set to false, any experimental/proposed APIs will throw errors.", description:
path: ["xterm", "allowProposedApi"], 'When set to false, any experimental/proposed APIs will throw errors.',
}, path: ['xterm', 'allowProposedApi'],
{ },
type: "boolean", {
name: "Allow Transparent Background", type: 'boolean',
description: "Whether the background is allowed to be a non-opaque color.", name: 'Allow Transparent Background',
path: ["xterm", "allowTransparency"], description: 'Whether the background is allowed to be a non-opaque color.',
}, path: ['xterm', 'allowTransparency'],
{ },
type: "text", {
name: "Bell Sound URI", type: 'text',
description: "URI for a custom bell character sound.", name: 'Bell Sound URI',
path: ["xterm", "bellSound"], description: 'URI for a custom bell character sound.',
nullable: true, path: ['xterm', 'bellSound'],
}, nullable: true,
{ },
type: "enum", {
name: "Bell Style", type: 'enum',
description: "How the terminal will react to the bell character", name: 'Bell Style',
path: ["xterm", "bellStyle"], description: 'How the terminal will react to the bell character',
enum: ["none", "sound"], path: ['xterm', 'bellStyle'],
}, enum: ['none', 'sound'],
{ },
type: "boolean", {
name: "Force End-Of-Line", type: 'boolean',
description: "When enabled, any new-line characters (\\n) will be interpreted as carriage-return new-line. (\\r\\n) Typically this is done by the shell program.", name: 'Force End-Of-Line',
path: ["xterm", "convertEol"], description:
}, 'When enabled, any new-line characters (\\n) will be interpreted as carriage-return new-line. (\\r\\n) Typically this is done by the shell program.',
{ path: ['xterm', 'convertEol'],
type: "boolean", },
name: "Disable Stdin", {
description: "Whether input should be disabled", type: 'boolean',
path: ["xterm", "disableStdin"], name: 'Disable Stdin',
}, description: 'Whether input should be disabled',
{ path: ['xterm', 'disableStdin'],
type: "number", },
name: "Letter Spacing", {
description: "The spacing in whole pixels between characters.", type: 'number',
path: ["xterm", "letterSpacing"], name: 'Letter Spacing',
}, description: 'The spacing in whole pixels between characters.',
{ path: ['xterm', 'letterSpacing'],
type: "number", },
name: "Line Height", {
description: "Line height, multiplied by the font size to get the height of terminal rows.", type: 'number',
path: ["xterm", "lineHeight"], name: 'Line Height',
float: true, description:
}, 'Line height, multiplied by the font size to get the height of terminal rows.',
{ path: ['xterm', 'lineHeight'],
type: "enum", float: true,
name: "XTerm Log Level", },
description: "Log level for the XTerm library.", {
path: ["xterm", "logLevel"], type: 'enum',
enum: ["debug", "info", "warn", "error", "off"], name: 'XTerm Log Level',
}, description: 'Log level for the XTerm library.',
{ path: ['xterm', 'logLevel'],
type: "boolean", enum: ['debug', 'info', 'warn', 'error', 'off'],
name: "Macintosh Option Key as Meta Key", },
description: "When enabled, the Option key on Macs will be interpreted as the Meta key.", {
path: ["xterm", "macOptionIsMeta"], type: 'boolean',
}, name: 'Macintosh Option Key as Meta Key',
{ description:
type: "boolean", 'When enabled, the Option key on Macs will be interpreted as the Meta key.',
name: "Macintosh Option Click Forces Selection", path: ['xterm', 'macOptionIsMeta'],
description: "Whether holding a modifier key will force normal selection behavior, regardless of whether the terminal is in mouse events mode. This will also prevent mouse events from being emitted by the terminal. For example, this allows you to use xterm.js' regular selection inside tmux with mouse mode enabled.", },
path: ["xterm", "macOptionClickForcesSelection"], {
}, type: 'boolean',
{ name: 'Macintosh Option Click Forces Selection',
type: "number", description:
name: "Forced Contrast Ratio", "Whether holding a modifier key will force normal selection behavior, regardless of whether the terminal is in mouse events mode. This will also prevent mouse events from being emitted by the terminal. For example, this allows you to use xterm.js' regular selection inside tmux with mouse mode enabled.",
description: "Miminum contrast ratio for terminal text. This will alter the foreground color dynamically to ensure the ratio is met. Goes from 1 (do nothing) to 21 (strict black and white).", path: ['xterm', 'macOptionClickForcesSelection'],
path: ["xterm", "minimumContrastRatio"], },
float: true, {
}, type: 'number',
{ name: 'Forced Contrast Ratio',
type: "enum", description:
name: "Renderer Type", 'Miminum contrast ratio for terminal text. This will alter the foreground color dynamically to ensure the ratio is met. Goes from 1 (do nothing) to 21 (strict black and white).',
description: "The terminal renderer to use. Canvas is preferred, but a DOM renderer is also available. Note: Letter spacing and cursor blink do not work in the DOM renderer.", path: ['xterm', 'minimumContrastRatio'],
path: ["xterm", "rendererType"], float: true,
enum: ["canvas", "dom"], },
}, {
{ type: 'enum',
type: "boolean", name: 'Renderer Type',
name: "Right Click Selects Words", description:
description: "Whether to select the word under the cursor on right click.", 'The terminal renderer to use. Canvas is preferred, but a DOM renderer is also available. Note: Letter spacing and cursor blink do not work in the DOM renderer.',
path: ["xterm", "rightClickSelectsWord"], path: ['xterm', 'rendererType'],
}, enum: ['canvas', 'dom'],
{ },
type: "boolean", {
name: "Screen Reader Support", type: 'boolean',
description: "Whether screen reader support is enabled. When on this will expose supporting elements in the DOM to support NVDA on Windows and VoiceOver on macOS.", name: 'Right Click Selects Words',
path: ["xterm", "screenReaderMode"], description: 'Whether to select the word under the cursor on right click.',
}, path: ['xterm', 'rightClickSelectsWord'],
{ },
type: "number", {
name: "Tab Stop Width", type: 'boolean',
description: "The size of tab stops in the terminal.", name: 'Screen Reader Support',
path: ["xterm", "tabStopWidth"], description:
}, 'Whether screen reader support is enabled. When on this will expose supporting elements in the DOM to support NVDA on Windows and VoiceOver on macOS.',
{ path: ['xterm', 'screenReaderMode'],
type: "boolean", },
name: "Windows Mode", {
description: "\"Whether 'Windows mode' is enabled. Because Windows backends winpty and conpty operate by doing line wrapping on their side, xterm.js does not have access to wrapped lines. When Windows mode is enabled the following changes will be in effect:\n- Reflow is disabled.\n- Lines are assumed to be wrapped if the last character of the line is not whitespace.", type: 'number',
path: ["xterm", "windowsMode"], name: 'Tab Stop Width',
}, description: 'The size of tab stops in the terminal.',
{ path: ['xterm', 'tabStopWidth'],
type: "text", },
name: "Word Separator", {
description: "All characters considered word separators. Used for double-click to select word logic. Encoded as JSON in this editor for editing convienience.", type: 'boolean',
path: ["xterm", "wordSeparator"], name: 'Windows Mode',
json: true, description:
} "\"Whether 'Windows mode' is enabled. Because Windows backends winpty and conpty operate by doing line wrapping on their side, xterm.js does not have access to wrapped lines. When Windows mode is enabled the following changes will be in effect:\n- Reflow is disabled.\n- Lines are assumed to be wrapped if the last character of the line is not whitespace.",
path: ['xterm', 'windowsMode'],
},
{
type: 'text',
name: 'Word Separator',
description:
'All characters considered word separators. Used for double-click to select word logic. Encoded as JSON in this editor for editing convienience.',
path: ['xterm', 'wordSeparator'],
json: true,
},
]); ]);

280
src/assets/xterm_config/xterm_color_theme.js

@ -1,144 +1,156 @@
const selectionColorOption = { const selectionColorOption = {
type: "color", type: 'color',
name: "Selection Color", name: 'Selection Color',
description: "Background color for selected text. Can be transparent.", description: 'Background color for selected text. Can be transparent.',
path: ["xterm", "theme", "selection"], path: ['xterm', 'theme', 'selection'],
}; };
const selectionColorOpacityOption = { const selectionColorOpacityOption = {
type: "number", type: 'number',
name: "Selection Color Opacity", name: 'Selection Color Opacity',
description: "Opacity of the selection highlight. A value between 1 (fully opaque) and 0 (fully transparent).", description:
path: ["wettyVoid"], 'Opacity of the selection highlight. A value between 1 (fully opaque) and 0 (fully transparent).',
float: true, path: ['wettyVoid'],
min: 0, float: true,
max: 1, min: 0,
max: 1,
}; };
window.inflateOptions([ window.inflateOptions([
{ {
type: "color", type: 'color',
name: "Foreground Color", name: 'Foreground Color',
description: "The default foreground (text) color.", description: 'The default foreground (text) color.',
path: ["xterm", "theme", "foreground"], path: ['xterm', 'theme', 'foreground'],
}, },
{ {
type: "color", type: 'color',
name: "Background Color", name: 'Background Color',
description: "The default background color.", description: 'The default background color.',
path: ["xterm", "theme", "background"], path: ['xterm', 'theme', 'background'],
}, },
{ {
type: "color", type: 'color',
name: "Cursor Color", name: 'Cursor Color',
description: "Color of the cursor.", description: 'Color of the cursor.',
path: ["xterm", "theme", "cursor"], path: ['xterm', 'theme', 'cursor'],
}, },
{ {
type: "color", type: 'color',
name: "Block Cursor Accent Color", name: 'Block Cursor Accent Color',
description: "The accent color of the cursor, used as the foreground color for block cursors.", description:
path: ["xterm", "theme", "cursorAccent"], 'The accent color of the cursor, used as the foreground color for block cursors.',
}, path: ['xterm', 'theme', 'cursorAccent'],
selectionColorOption, },
selectionColorOpacityOption, selectionColorOption,
{ selectionColorOpacityOption,
type: "color", {
name: "Black", type: 'color',
description: "Color for ANSI Black text.", name: 'Black',
path: ["xterm", "theme", "black"], description: 'Color for ANSI Black text.',
}, path: ['xterm', 'theme', 'black'],
{ },
type: "color", {
name: "Red", type: 'color',
description: "Color for ANSI Red text.", name: 'Red',
path: ["xterm", "theme", "red"], description: 'Color for ANSI Red text.',
}, path: ['xterm', 'theme', 'red'],
{ },
type: "color", {
name: "Green", type: 'color',
description: "Color for ANSI Green text.", name: 'Green',
path: ["xterm", "theme", "green"], description: 'Color for ANSI Green text.',
}, path: ['xterm', 'theme', 'green'],
{ },
type: "color", {
name: "Yellow", type: 'color',
description: "Color for ANSI Yellow text.", name: 'Yellow',
path: ["xterm", "theme", "yellow"], description: 'Color for ANSI Yellow text.',
}, path: ['xterm', 'theme', 'yellow'],
{ },
type: "color", {
name: "Blue", type: 'color',
description: "Color for ANSI Blue text.", name: 'Blue',
path: ["xterm", "theme", "blue"], description: 'Color for ANSI Blue text.',
}, path: ['xterm', 'theme', 'blue'],
{ },
type: "color", {
name: "Magenta", type: 'color',
description: "Color for ANSI Magenta text.", name: 'Magenta',
path: ["xterm", "theme", "magenta"], description: 'Color for ANSI Magenta text.',
}, path: ['xterm', 'theme', 'magenta'],
{ },
type: "color", {
name: "Cyan", type: 'color',
description: "Color for ANSI Cyan text.", name: 'Cyan',
path: ["xterm", "theme", "cyan"], description: 'Color for ANSI Cyan text.',
}, path: ['xterm', 'theme', 'cyan'],
{ },
type: "color", {
name: "White", type: 'color',
description: "Color for ANSI White text.", name: 'White',
path: ["xterm", "theme", "white"], description: 'Color for ANSI White text.',
}, path: ['xterm', 'theme', 'white'],
{ },
type: "color", {
name: "Bright Black", type: 'color',
description: "Color for ANSI Bright Black text.", name: 'Bright Black',
path: ["xterm", "theme", "brightBlack"], description: 'Color for ANSI Bright Black text.',
}, path: ['xterm', 'theme', 'brightBlack'],
{ },
type: "color", {
name: "Bright Red", type: 'color',
description: "Color for ANSI Bright Red text.", name: 'Bright Red',
path: ["xterm", "theme", "brightRed"], description: 'Color for ANSI Bright Red text.',
}, path: ['xterm', 'theme', 'brightRed'],
{ },
type: "color", {
name: "Bright Green", type: 'color',
description: "Color for ANSI Bright Green text.", name: 'Bright Green',
path: ["xterm", "theme", "brightGreen"], description: 'Color for ANSI Bright Green text.',
}, path: ['xterm', 'theme', 'brightGreen'],
{ },
type: "color", {
name: "Bright Yellow", type: 'color',
description: "Color for ANSI Bright Yellow text.", name: 'Bright Yellow',
path: ["xterm", "theme", "brightYellow"], description: 'Color for ANSI Bright Yellow text.',
}, path: ['xterm', 'theme', 'brightYellow'],
{ },
type: "color", {
name: "Bright Blue", type: 'color',
description: "Color for ANSI Bright Blue text.", name: 'Bright Blue',
path: ["xterm", "theme", "brightBlue"], description: 'Color for ANSI Bright Blue text.',
}, path: ['xterm', 'theme', 'brightBlue'],
{ },
type: "color", {
name: "Bright Magenta", type: 'color',
description: "Color for ANSI Bright Magenta text.", name: 'Bright Magenta',
path: ["xterm", "theme", "brightMagenta"], description: 'Color for ANSI Bright Magenta text.',
}, path: ['xterm', 'theme', 'brightMagenta'],
{ },
type: "color", {
name: "Bright White", type: 'color',
description: "Color for ANSI Bright White text.", name: 'Bright White',
path: ["xterm", "theme", "brightWhite"], description: 'Color for ANSI Bright White text.',
} path: ['xterm', 'theme', 'brightWhite'],
},
]); ]);
selectionColorOption.get = function() { selectionColorOption.get = function () {
return this.el.querySelector("input").value + Math.round(selectionColorOpacityOption.el.querySelector("input").value * 255).toString(16); return (
this.el.querySelector('input').value +
Math.round(
selectionColorOpacityOption.el.querySelector('input').value * 255,
).toString(16)
);
}; };
selectionColorOption.set = function(value) { selectionColorOption.set = function (value) {
this.el.querySelector("input").value = value.substring(0, 7); this.el.querySelector('input').value = value.substring(0, 7);
selectionColorOpacityOption.el.querySelector("input").value = Math.round(parseInt(value.substring(7), 16) / 255 * 100) / 100; selectionColorOpacityOption.el.querySelector('input').value =
Math.round((parseInt(value.substring(7), 16) / 255) * 100) / 100;
};
selectionColorOpacityOption.get = function () {
return 0;
};
selectionColorOpacityOption.set = function () {
return 0;
}; };
selectionColorOpacityOption.get = function() { return 0; };
selectionColorOpacityOption.set = function() { return 0; };

129
src/assets/xterm_config/xterm_defaults.js

@ -1,69 +1,70 @@
const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; const DEFAULT_BELL_SOUND =
'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
window.loadOptions({ window.loadOptions({
wettyFitTerminal: true, wettyFitTerminal: true,
wettyVoid: 0, wettyVoid: 0,
xterm: { xterm: {
cols: 80, cols: 80,
rows: 24, rows: 24,
cursorBlink: false, cursorBlink: false,
cursorStyle: 'block', cursorStyle: 'block',
cursorWidth: 1, cursorWidth: 1,
bellSound: DEFAULT_BELL_SOUND, bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none', bellStyle: 'none',
drawBoldTextInBrightColors: true, drawBoldTextInBrightColors: true,
fastScrollModifier: 'alt', fastScrollModifier: 'alt',
fastScrollSensitivity: 5, fastScrollSensitivity: 5,
fontFamily: 'courier-new, courier, monospace', fontFamily: 'courier-new, courier, monospace',
fontSize: 15, fontSize: 15,
fontWeight: 'normal', fontWeight: 'normal',
fontWeightBold: 'bold', fontWeightBold: 'bold',
lineHeight: 1.0, lineHeight: 1.0,
linkTooltipHoverDuration: 500, linkTooltipHoverDuration: 500,
letterSpacing: 0, letterSpacing: 0,
logLevel: 'info', logLevel: 'info',
scrollback: 1000, scrollback: 1000,
scrollSensitivity: 1, scrollSensitivity: 1,
screenReaderMode: false, screenReaderMode: false,
macOptionIsMeta: false, macOptionIsMeta: false,
macOptionClickForcesSelection: false, macOptionClickForcesSelection: false,
minimumContrastRatio: 1, minimumContrastRatio: 1,
disableStdin: false, disableStdin: false,
allowProposedApi: true, allowProposedApi: true,
allowTransparency: false, allowTransparency: false,
tabStopWidth: 8, tabStopWidth: 8,
rightClickSelectsWord: false, rightClickSelectsWord: false,
rendererType: 'canvas', rendererType: 'canvas',
windowOptions: {}, windowOptions: {},
windowsMode: false, windowsMode: false,
wordSeparator: ' ()[]{}\',"`', wordSeparator: ' ()[]{}\',"`',
convertEol: false, convertEol: false,
termName: 'xterm', termName: 'xterm',
cancelEvents: false, cancelEvents: false,
theme: { theme: {
foreground: "#ffffff", foreground: '#ffffff',
background: "#000000", background: '#000000',
cursor: "#ffffff", cursor: '#ffffff',
cursorAccent: "#000000", cursorAccent: '#000000',
selection: "#FFFFFF4D", selection: '#FFFFFF4D',
black: "#2e3436", black: '#2e3436',
red: "#cc0000", red: '#cc0000',
green: "#4e9a06", green: '#4e9a06',
yellow: "#c4a000", yellow: '#c4a000',
blue: "#3465a4", blue: '#3465a4',
magenta: "#75507b", magenta: '#75507b',
cyan: "#06989a", cyan: '#06989a',
white: "#d3d7cf", white: '#d3d7cf',
brightBlack: "#555753", brightBlack: '#555753',
brightRed: "#ef2929", brightRed: '#ef2929',
brightGreen: "#8ae234", brightGreen: '#8ae234',
brightYellow: "#fce94f", brightYellow: '#fce94f',
brightBlue: "#729fcf", brightBlue: '#729fcf',
brightMagenta: "#ad7fa8", brightMagenta: '#ad7fa8',
brightCyan: "#34e2e2", brightCyan: '#34e2e2',
brightWhite: "#eeeeec" brightWhite: '#eeeeec',
} },
} },
}); });

239
src/assets/xterm_config/xterm_general_options.js

@ -1,107 +1,136 @@
window.inflateOptions([ window.inflateOptions([
{ {
type: "text", type: 'text',
name: "Font Family", name: 'Font Family',
description: "The font family for terminal text.", description: 'The font family for terminal text.',
path: ["xterm", "fontFamily"], path: ['xterm', 'fontFamily'],
}, },
{ {
type: "number", type: 'number',
name: "Font Size", name: 'Font Size',
description: "The font size in CSS pixels for terminal text.", description: 'The font size in CSS pixels for terminal text.',
path: ["xterm", "fontSize"], path: ['xterm', 'fontSize'],
min: 4, min: 4,
}, },
{ {
type: "enum", type: 'enum',
name: "Regular Font Weight", name: 'Regular Font Weight',
description: "The font weight for non-bold text.", description: 'The font weight for non-bold text.',
path: ["xterm", "fontWeight"], path: ['xterm', 'fontWeight'],
enum: ["normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"], enum: [
}, 'normal',
{ 'bold',
type: "enum", '100',
name: "Bold Font Weight", '200',
description: "The font weight for bold text.", '300',
path: ["xterm", "fontWeightBold"], '400',
enum: ["normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"], '500',
}, '600',
{ '700',
type: "boolean", '800',
name: "Fit Terminal", '900',
description: "Automatically fits the terminal to the page, overriding terminal columns and rows.", ],
path: ["wettyFitTerminal"], },
}, {
{ type: 'enum',
type: "number", name: 'Bold Font Weight',
name: "Terminal Columns", description: 'The font weight for bold text.',
description: "The number of columns in the terminal. Overridden by the Fit Terminal option.", path: ['xterm', 'fontWeightBold'],
path: ["xterm", "cols"], enum: [
nullable: true, 'normal',
}, 'bold',
{ '100',
type: "number", '200',
name: "Terminal Rows", '300',
description: "The number of rows in the terminal. Overridden by the Fit Terminal option.", '400',
path: ["xterm", "rows"], '500',
nullable: true, '600',
}, '700',
{ '800',
type: "enum", '900',
name: "Cursor Style", ],
description: "The style of the cursor", },
path: ["xterm", "cursorStyle"], {
enum: ["block", "underline", "bar"], type: 'boolean',
}, name: 'Fit Terminal',
{ description:
type: "boolean", 'Automatically fits the terminal to the page, overriding terminal columns and rows.',
name: "Blinking Cursor", path: ['wettyFitTerminal'],
description: "Whether the cursor blinks", },
path: ["xterm", "cursorBlink"], {
}, type: 'number',
{ name: 'Terminal Columns',
type: "number", description:
name: "Bar Cursor Width", 'The number of columns in the terminal. Overridden by the Fit Terminal option.',
description: "The width of the cursor in CSS pixels. Only applies when Cursor Style is set to 'bar'.", path: ['xterm', 'cols'],
path: ["xterm", "cursorWidth"], nullable: true,
}, },
{ {
type: "boolean", type: 'number',
name: "Draw Bold Text In Bright Colors", name: 'Terminal Rows',
description: "Whether to draw bold text in bright colors", description:
path: ["xterm", "drawBoldTextInBrightColors"], 'The number of rows in the terminal. Overridden by the Fit Terminal option.',
}, path: ['xterm', 'rows'],
{ nullable: true,
type: "number", },
name: "Scroll Sensitivity", {
description: "The scroll speed multiplier for regular scrolling.", type: 'enum',
path: ["xterm", "scrollSensitivity"], name: 'Cursor Style',
float: true, description: 'The style of the cursor',
}, path: ['xterm', 'cursorStyle'],
{ enum: ['block', 'underline', 'bar'],
type: "enum", },
name: "Fast Scroll Key", {
description: "The modifier key to hold to multiply scroll speed.", type: 'boolean',
path: ["xterm", "fastScrollModifier"], name: 'Blinking Cursor',
enum: ["none", "alt", "shift", "ctrl"], description: 'Whether the cursor blinks',
}, path: ['xterm', 'cursorBlink'],
{ },
type: "number", {
name: "Fast Scroll Multiplier", type: 'number',
description: "The scroll speed multiplier used for fast scrolling.", name: 'Bar Cursor Width',
path: ["xterm", "fastScrollSensitivity"], description:
float: true, "The width of the cursor in CSS pixels. Only applies when Cursor Style is set to 'bar'.",
}, path: ['xterm', 'cursorWidth'],
{ },
type: "number", {
name: "Scrollback Rows", type: 'boolean',
description: "The amount of scrollback rows, rows you can scroll up to after they leave the viewport, to keep.", name: 'Draw Bold Text In Bright Colors',
path: ["xterm", "scrollback"], description: 'Whether to draw bold text in bright colors',
}, path: ['xterm', 'drawBoldTextInBrightColors'],
{ },
type: "number", {
name: "Tab Stop Width", type: 'number',
description: "The size of tab stops in the terminal.", name: 'Scroll Sensitivity',
path: ["xterm", "tabStopWidth"], description: 'The scroll speed multiplier for regular scrolling.',
}, path: ['xterm', 'scrollSensitivity'],
float: true,
},
{
type: 'enum',
name: 'Fast Scroll Key',
description: 'The modifier key to hold to multiply scroll speed.',
path: ['xterm', 'fastScrollModifier'],
enum: ['none', 'alt', 'shift', 'ctrl'],
},
{
type: 'number',
name: 'Fast Scroll Multiplier',
description: 'The scroll speed multiplier used for fast scrolling.',
path: ['xterm', 'fastScrollSensitivity'],
float: true,
},
{
type: 'number',
name: 'Scrollback Rows',
description:
'The amount of scrollback rows, rows you can scroll up to after they leave the viewport, to keep.',
path: ['xterm', 'scrollback'],
},
{
type: 'number',
name: 'Tab Stop Width',
description: 'The size of tab stops in the terminal.',
path: ['xterm', 'tabStopWidth'],
},
]); ]);

4
src/buffer.ts

@ -7,8 +7,8 @@ function ask(question: string): Promise<string> {
input: process.stdin, input: process.stdin,
output: process.stdout, output: process.stdout,
}); });
return new Promise(resolve => { return new Promise((resolve) => {
rlp.question(`${question}: `, answer => { rlp.question(`${question}: `, (answer) => {
rlp.close(); rlp.close();
resolve(answer); resolve(answer);
}); });

4
src/client/dev.ts

@ -1,5 +1,5 @@
caches.keys().then(cacheNames => { caches.keys().then((cacheNames) => {
cacheNames.forEach(cacheName => { cacheNames.forEach((cacheName) => {
caches.delete(cacheName); caches.delete(cacheName);
}); });
}); });

33
src/client/wetty/term/confiruragtion.ts

@ -9,22 +9,35 @@ import { loadOptions } from './confiruragtion/load';
export function configureTerm(term: Term): void { export function configureTerm(term: Term): void {
let options = loadOptions(); let options = loadOptions();
// Convert old options to new options // Convert old options to new options
if (!("xterm" in options)) options = { xterm: options }; if (!('xterm' in options)) options = { xterm: options };
try { setOptions(term, options); } catch { /* Do nothing */ }; try {
setOptions(term, options);
} catch {
/* Do nothing */
}
const toggle = document.querySelector('#options .toggler'); const toggle = document.querySelector('#options .toggler');
const optionsElem = document.getElementById('options'); const optionsElem = document.getElementById('options');
if (editor == null || toggle == null || optionsElem == null) throw new Error("Couldn't initialize configuration menu"); if (editor == null || toggle == null || optionsElem == null)
throw new Error("Couldn't initialize configuration menu");
function editorOnLoad() { function editorOnLoad() {
(editor.contentWindow as any).loadOptions(loadOptions()); (editor.contentWindow as any).loadOptions(loadOptions());
(editor.contentWindow as any).wetty_close_config = () => { optionsElem!.classList.toggle('opened'); }; (editor.contentWindow as any).wetty_close_config = () => {
(editor.contentWindow as any).wetty_save_config = (newConfig: any) => { onInput(term, newConfig); }; optionsElem!.classList.toggle('opened');
};
(editor.contentWindow as any).wetty_save_config = (newConfig: any) => {
onInput(term, newConfig);
};
} }
if ((editor.contentDocument || editor.contentWindow!.document).readyState === "complete") editorOnLoad(); if (
editor.addEventListener("load", editorOnLoad); (editor.contentDocument || editor.contentWindow!.document).readyState ===
'complete'
toggle.addEventListener('click', e => { )
editorOnLoad();
editor.addEventListener('load', editorOnLoad);
toggle.addEventListener('click', (e) => {
(editor.contentWindow as any).loadOptions(loadOptions()); (editor.contentWindow as any).loadOptions(loadOptions());
optionsElem.classList.toggle('opened'); optionsElem.classList.toggle('opened');
e.preventDefault(); e.preventDefault();
@ -42,5 +55,5 @@ export function configureTerm(term: Term): void {
} }
export function shouldFitTerm(): boolean { export function shouldFitTerm(): boolean {
return (loadOptions() as any).wettyFitTerminal ?? true; return (loadOptions() as any).wettyFitTerminal ?? true;
} }

15
src/client/wetty/term/confiruragtion/editor.ts

@ -5,21 +5,26 @@ export const onInput = (term: Term, updated: any) => {
try { try {
const updatedConf = JSON.stringify(updated, null, 2); const updatedConf = JSON.stringify(updated, null, 2);
if (localStorage.options === updatedConf) return; if (localStorage.options === updatedConf) return;
setOptions(term, updated); setOptions(term, updated);
if (!updated.wettyFitTerminal && updated.xterm.cols != null && updated.xterm.rows != null) term.resize(updated.xterm.cols, updated.xterm.rows); if (
!updated.wettyFitTerminal &&
updated.xterm.cols != null &&
updated.xterm.rows != null
)
term.resize(updated.xterm.cols, updated.xterm.rows);
term.resizeTerm(); term.resizeTerm();
editor.classList.remove('error'); editor.classList.remove('error');
localStorage.options = updatedConf; localStorage.options = updatedConf;
} catch (e) { } catch (e) {
console.error("Configuration Error"); console.error('Configuration Error');
console.error(e); console.error(e);
editor.classList.add('error'); editor.classList.add('error');
} }
}; };
export function setOptions(term: Term, options: any) { export function setOptions(term: Term, options: any) {
Object.keys(options.xterm).forEach(key => { Object.keys(options.xterm).forEach((key) => {
if (key === "cols" || key === "rows") return; if (key === 'cols' || key === 'rows') return;
const value = options.xterm[key]; const value = options.xterm[key];
term.setOption(key, value); term.setOption(key, value);
}); });

7
src/main.ts

@ -56,7 +56,8 @@ const opts = yargs
type: 'string', type: 'string',
}) })
.option('ssh-config', { .option('ssh-config', {
description: 'Specifies an alternative ssh configuration file. For further details see "-F" option in ssh(1)', description:
'Specifies an alternative ssh configuration file. For further details see "-F" option in ssh(1)',
type: 'string', type: 'string',
}) })
.option('force-ssh', { .option('force-ssh', {
@ -100,8 +101,8 @@ const opts = yargs
if (!opts.help) { if (!opts.help) {
loadConfigFile(opts.conf) loadConfigFile(opts.conf)
.then(config => mergeCliConf(opts, config)) .then((config) => mergeCliConf(opts, config))
.then(conf => .then((conf) =>
start(conf.ssh, conf.server, conf.command, conf.forceSSH, conf.ssl), start(conf.ssh, conf.server, conf.command, conf.forceSSH, conf.ssl),
) )
.catch((err: Error) => { .catch((err: Error) => {

10
src/server/socketServer/html.ts

@ -18,7 +18,7 @@ const render = (
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="icon" type="image/x-icon" href="${favicon}"> <link rel="icon" type="image/x-icon" href="${favicon}">
<title>${title}</title> <title>${title}</title>
${css.map(file => `<link rel="stylesheet" href="${file}" />`).join('\n')} ${css.map((file) => `<link rel="stylesheet" href="${file}" />`).join('\n')}
</head> </head>
<body> <body>
<div id="overlay"> <div id="overlay">
@ -36,7 +36,7 @@ const render = (
</div> </div>
<div id="terminal"></div> <div id="terminal"></div>
${js ${js
.map(file => `<script type="module" src="${file}"></script>`) .map((file) => `<script type="module" src="${file}"></script>`)
.join('\n')} .join('\n')}
</body> </body>
</html>`; </html>`;
@ -49,9 +49,9 @@ export const html = (base: string, title: string): RequestHandler => (
render( render(
title, title,
`${base}/favicon.ico`, `${base}/favicon.ico`,
cssFiles.map(css => `${base}/assets/css/${css}.css`), cssFiles.map((css) => `${base}/assets/css/${css}.css`),
jsFiles.map(js => `${base}/client/${js}.js`), jsFiles.map((js) => `${base}/client/${js}.js`),
`${base}/assets/xterm_config/index.html`, `${base}/assets/xterm_config/index.html`,
), ),
); );
}; };

2
src/server/spawn.ts

@ -30,7 +30,7 @@ export function spawn(socket: SocketIO.Socket, args: string[]): void {
.on('resize', ({ cols, rows }) => { .on('resize', ({ cols, rows }) => {
term.resize(cols, rows); term.resize(cols, rows);
}) })
.on('input', input => { .on('input', (input) => {
if (!isUndefined(term)) term.write(input); if (!isUndefined(term)) term.write(input);
}) })
.on('disconnect', () => { .on('disconnect', () => {

Loading…
Cancel
Save