diff --git a/src/assets/scss/options.scss b/src/assets/scss/options.scss
index dc3109b..a207610 100644
--- a/src/assets/scss/options.scss
+++ b/src/assets/scss/options.scss
@@ -39,8 +39,8 @@
}
#options.opened {
- height: 50%;
- width: 50%;
+ height: max(min(300px, 75vh), 50vh);
+ width: max(min(500px, 75vw), 50vw);
.editor {
display: flex;
diff --git a/src/assets/xterm_config/functionality.js b/src/assets/xterm_config/functionality.js
new file mode 100644
index 0000000..61c34e8
--- /dev/null
+++ b/src/assets/xterm_config/functionality.js
@@ -0,0 +1,126 @@
+function optionGenericGet() { return this.el.querySelector("input").value; }
+function optionGenericSet(value) { this.el.querySelector("input").value = 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() {
+ let value = (this.float === true ? parseFloat : parseInt)(this.el.querySelector("input").value);
+ if (Number.isNaN(value) || typeof value !== "number") value = 0;
+ 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; }
+
+const allOptions = [];
+function inflateOptions(optionsSchema) {
+ const booleanOption = document.querySelector("#boolean_option.templ");
+ const enumOption = document.querySelector("#enum_option.templ");
+ const textOption = document.querySelector("#text_option.templ");
+ const numberOption = document.querySelector("#number_option.templ");
+ const colorOption = document.querySelector("#color_option.templ");
+
+ function copyOver(element) {
+ while (element.children.length > 0) document.body.append(element.children[0]);
+ }
+
+ optionsSchema.forEach(option => {
+ let el;
+ option.get = optionGenericGet.bind(option);
+ option.set = optionGenericSet.bind(option);
+
+ switch (option.type) {
+ case "boolean":
+ el = booleanOption.cloneNode(true);
+ option.get = optionBoolGet.bind(option);
+ option.set = optionBoolSet.bind(option);
+ break;
+
+ case "enum":
+ el = enumOption.cloneNode(true);
+ option.enum.forEach(varriant => {
+ const optionEl = document.createElement("option");
+ optionEl.innerText = varriant;
+ optionEl.value = varriant;
+ el.querySelector("select").appendChild(optionEl);
+ });
+ option.get = optionEnumGet.bind(option);
+ option.set = optionEnumSet.bind(option);
+ break;
+
+ case "text":
+ el = textOption.cloneNode(true);
+ break;
+
+ case "number":
+ el = numberOption.cloneNode(true);
+ if (option.float === true) el.querySelector("input").setAttribute("step", "0.001");
+ option.get = optionNumberGet.bind(option);
+ option.set = optionNumberSet.bind(option);
+ if (typeof option.min === "number") el.querySelector("input").setAttribute("min", option.min.toString());
+ if (typeof option.max === "number") el.querySelector("input").setAttribute("max", option.max.toString());
+ break;
+
+ case "color":
+ el = colorOption.cloneNode(true);
+ break;
+
+ default:
+ throw new Error(`Unknown option type ${ option.type}`);
+ }
+
+ el.querySelector(".title").innerText = option.name;
+ el.querySelector(".desc").innerText = option.description;
+ [ option.el ] = el.children;
+ copyOver(el);
+ allOptions.push(option);
+ });
+}
+
+function getItem(json, path) {
+ const mypath = path[0];
+ if (path.length === 1) return json[mypath];
+ if (json[mypath] != null) return getItem(json[mypath], path.slice(1));
+ return null;
+}
+function setItem(json, path, item) {
+ const mypath = path[0];
+ if (path.length === 1) json[mypath] = item;
+ else {
+ if (json[mypath] == null) json[mypath] = {};
+ setItem(json[mypath], path.slice(1), item);
+ }
+}
+
+window.loadOptions = function(config) {
+ allOptions.forEach(option => {
+ let value = getItem(config, option.path);
+ if (option.nullable === true && option.type === "text" && value == null) value = null;
+ else if (option.nullable === true && option.type === "number" && 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.");
+
+function saveConfig() {
+ const newConfig = {};
+ allOptions.forEach(option => {
+ let newValue = option.get();
+ if (option.nullable === true && ((option.type === "text" && newValue === "") || ( option.type === "number" && newValue < 0))) 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", () => {
+ const els = document.querySelectorAll("input, select");
+ for (let i = 0; i < els.length; i += 1) {
+ els[i].addEventListener("input", saveConfig);
+ }
+});
diff --git a/src/assets/xterm_config/index.html b/src/assets/xterm_config/index.html
new file mode 100644
index 0000000..16cdf97
--- /dev/null
+++ b/src/assets/xterm_config/index.html
@@ -0,0 +1,71 @@
+
+
+
+ Wetty XTerm Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+ General Options
+
+ Color Theme
+
+ Advanced XTerm Options
+
+
+
+
+
diff --git a/src/assets/xterm_config/style.css b/src/assets/xterm_config/style.css
new file mode 100644
index 0000000..b5b866b
--- /dev/null
+++ b/src/assets/xterm_config/style.css
@@ -0,0 +1,59 @@
+html { background-color: black; }
+html, body { overflow: hidden auto; }
+body {
+ display: flex;
+ flex-flow: column nowrap;
+ font-family: monospace;
+ font-size: 1rem;
+ color: white;
+}
+.templ { display: none; }
+h2 { text-align: center; text-decoration: underline; }
+
+header {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+}
+header button {
+ padding: 0.5em;
+ font-size: 1em;
+ margin: 0.5em;
+ border-radius: 0.5em;
+ collapse-margin;
+}
+
+.boolean_option, .number_option, .color_option, .enum_option, .text_option {
+ display: grid;
+ grid-template-columns: 100fr min(30em, 50%);
+ grid-template-rows: auto;
+ align-items: center;
+}
+.boolean_option input, .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 {
+ padding: 0.4em;
+}
+.boolean_option input {
+ width: 2em;
+ height: 2em;
+ font-size: 0.75em;
+ justify-self: center;
+}
+.color_option input {
+ width: 100%;
+ height: 100%;
+ background-color: lightgray;
+}
+
+.unbounded .title::before {
+ content: "UNBOUND OPTION ";
+ color: red;
+ font-weight: bold;
+}
diff --git a/src/assets/xterm_config/xterm_advanced_options.js b/src/assets/xterm_config/xterm_advanced_options.js
new file mode 100644
index 0000000..c99bda7
--- /dev/null
+++ b/src/assets/xterm_config/xterm_advanced_options.js
@@ -0,0 +1,122 @@
+window.inflateOptions([
+ {
+ type: "boolean",
+ name: "Allow Proposed XTerm APIs",
+ description: "When set to false, any experimental/proposed APIs will throw errors.",
+ path: ["xterm", "allowProposedApi"],
+ },
+ {
+ type: "boolean",
+ name: "Allow Transparent Background",
+ description: "Whether the background is allowed to be a non-opaque color.",
+ path: ["xterm", "allowTransparency"],
+ },
+ {
+ type: "text",
+ name: "Bell Sound URI",
+ description: "URI for a custom bell character sound.",
+ path: ["xterm", "bellSound"],
+ nullable: true,
+ },
+ {
+ type: "enum",
+ name: "Bell Style",
+ description: "How the terminal will react to the bell character",
+ path: ["xterm", "bellStyle"],
+ enum: ["none", "sound"],
+ },
+ {
+ type: "boolean",
+ name: "Force End-Of-Line",
+ 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",
+ path: ["xterm", "disableStdin"],
+ },
+ {
+ type: "number",
+ 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.",
+ path: ["xterm", "lineHeight"],
+ float: true,
+ },
+ {
+ type: "enum",
+ name: "XTerm Log Level",
+ description: "Log level for the XTerm library.",
+ path: ["xterm", "logLevel"],
+ enum: ["debug", "info", "warn", "error", "off"],
+ },
+ {
+ type: "boolean",
+ 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 Click Forces Selection",
+ 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: "number",
+ name: "Forced Contrast Ratio",
+ 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", "minimumContrastRatio"],
+ float: true,
+ },
+ {
+ type: "enum",
+ name: "Renderer Type",
+ 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", "rendererType"],
+ enum: ["canvas", "dom"],
+ },
+ {
+ type: "boolean",
+ name: "Right Click Selects Words",
+ description: "Whether to select the word under the cursor on right click.",
+ path: ["xterm", "rightClickSelectsWord"],
+ },
+ {
+ type: "boolean",
+ name: "Screen Reader Support",
+ 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: "number",
+ name: "Tab Stop Width",
+ description: "The size of tab stops in the terminal.",
+ path: ["xterm", "tabStopWidth"],
+ },
+ {
+ 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.",
+ 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,
+ }
+]);
+
+
+
+
+
diff --git a/src/assets/xterm_config/xterm_color_theme.js b/src/assets/xterm_config/xterm_color_theme.js
new file mode 100644
index 0000000..05ca3b9
--- /dev/null
+++ b/src/assets/xterm_config/xterm_color_theme.js
@@ -0,0 +1,144 @@
+const selectionColorOption = {
+ type: "color",
+ name: "Selection Color",
+ description: "Background color for selected text. Can be transparent.",
+ path: ["xterm", "theme", "selection"],
+};
+const selectionColorOpacityOption = {
+ type: "number",
+ name: "Selection Color Opacity",
+ description: "Opacity of the selection highlight. A value between 1 (fully opaque) and 0 (fully transparent).",
+ path: ["wettyVoid"],
+ float: true,
+ min: 0,
+ max: 1,
+};
+
+window.inflateOptions([
+ {
+ type: "color",
+ name: "Foreground Color",
+ description: "The default foreground (text) color.",
+ path: ["xterm", "theme", "foreground"],
+ },
+ {
+ type: "color",
+ name: "Background Color",
+ description: "The default background color.",
+ path: ["xterm", "theme", "background"],
+ },
+ {
+ type: "color",
+ name: "Cursor Color",
+ description: "Color of the cursor.",
+ path: ["xterm", "theme", "cursor"],
+ },
+ {
+ type: "color",
+ name: "Block Cursor Accent Color",
+ description: "The accent color of the cursor, used as the foreground color for block cursors.",
+ path: ["xterm", "theme", "cursorAccent"],
+ },
+ selectionColorOption,
+ selectionColorOpacityOption,
+ {
+ type: "color",
+ name: "Black",
+ description: "Color for ANSI Black text.",
+ path: ["xterm", "theme", "black"],
+ },
+ {
+ type: "color",
+ name: "Red",
+ description: "Color for ANSI Red text.",
+ path: ["xterm", "theme", "red"],
+ },
+ {
+ type: "color",
+ name: "Green",
+ description: "Color for ANSI Green text.",
+ path: ["xterm", "theme", "green"],
+ },
+ {
+ type: "color",
+ name: "Yellow",
+ description: "Color for ANSI Yellow text.",
+ path: ["xterm", "theme", "yellow"],
+ },
+ {
+ type: "color",
+ name: "Blue",
+ description: "Color for ANSI Blue text.",
+ path: ["xterm", "theme", "blue"],
+ },
+ {
+ type: "color",
+ name: "Magenta",
+ description: "Color for ANSI Magenta text.",
+ path: ["xterm", "theme", "magenta"],
+ },
+ {
+ type: "color",
+ name: "Cyan",
+ description: "Color for ANSI Cyan text.",
+ path: ["xterm", "theme", "cyan"],
+ },
+ {
+ type: "color",
+ name: "White",
+ description: "Color for ANSI White text.",
+ path: ["xterm", "theme", "white"],
+ },
+ {
+ type: "color",
+ name: "Bright Black",
+ description: "Color for ANSI Bright Black text.",
+ path: ["xterm", "theme", "brightBlack"],
+ },
+ {
+ type: "color",
+ name: "Bright Red",
+ description: "Color for ANSI Bright Red text.",
+ path: ["xterm", "theme", "brightRed"],
+ },
+ {
+ type: "color",
+ name: "Bright Green",
+ description: "Color for ANSI Bright Green text.",
+ path: ["xterm", "theme", "brightGreen"],
+ },
+ {
+ type: "color",
+ name: "Bright Yellow",
+ description: "Color for ANSI Bright Yellow text.",
+ path: ["xterm", "theme", "brightYellow"],
+ },
+ {
+ type: "color",
+ name: "Bright Blue",
+ description: "Color for ANSI Bright Blue text.",
+ path: ["xterm", "theme", "brightBlue"],
+ },
+ {
+ type: "color",
+ name: "Bright Magenta",
+ description: "Color for ANSI Bright Magenta text.",
+ path: ["xterm", "theme", "brightMagenta"],
+ },
+ {
+ type: "color",
+ name: "Bright White",
+ description: "Color for ANSI Bright White text.",
+ path: ["xterm", "theme", "brightWhite"],
+ }
+]);
+
+selectionColorOption.get = function() {
+ return this.el.querySelector("input").value + Math.round(selectionColorOpacityOption.el.querySelector("input").value * 255).toString(16);
+};
+selectionColorOption.set = function(value) {
+ 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.get = function() { return 0; };
+selectionColorOpacityOption.set = function() { return 0; };
diff --git a/src/assets/xterm_config/xterm_defaults.js b/src/assets/xterm_config/xterm_defaults.js
new file mode 100644
index 0000000..0d2b2ce
--- /dev/null
+++ b/src/assets/xterm_config/xterm_defaults.js
@@ -0,0 +1,69 @@
+const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
+window.loadOptions({
+ wettyFitTerminal: true,
+ wettyVoid: 0,
+
+ xterm: {
+ cols: 80,
+ rows: 24,
+ cursorBlink: false,
+ cursorStyle: 'block',
+ cursorWidth: 1,
+ bellSound: DEFAULT_BELL_SOUND,
+ bellStyle: 'none',
+ drawBoldTextInBrightColors: true,
+ fastScrollModifier: 'alt',
+ fastScrollSensitivity: 5,
+ fontFamily: 'courier-new, courier, monospace',
+ fontSize: 15,
+ fontWeight: 'normal',
+ fontWeightBold: 'bold',
+ lineHeight: 1.0,
+ linkTooltipHoverDuration: 500,
+ letterSpacing: 0,
+ logLevel: 'info',
+ scrollback: 1000,
+ scrollSensitivity: 1,
+ screenReaderMode: false,
+ macOptionIsMeta: false,
+ macOptionClickForcesSelection: false,
+ minimumContrastRatio: 1,
+ disableStdin: false,
+ allowProposedApi: true,
+ allowTransparency: false,
+ tabStopWidth: 8,
+ rightClickSelectsWord: false,
+ rendererType: 'canvas',
+ windowOptions: {},
+ windowsMode: false,
+ wordSeparator: ' ()[]{}\',"`',
+ convertEol: false,
+ termName: 'xterm',
+ cancelEvents: false,
+
+ theme: {
+ foreground: "#ffffff",
+ background: "#000000",
+ cursor: "#ffffff",
+ cursorAccent: "#000000",
+ selection: "#FFFFFF4D",
+
+ black: "#2e3436",
+ red: "#cc0000",
+ green: "#4e9a06",
+ yellow: "#c4a000",
+ blue: "#3465a4",
+ magenta: "#75507b",
+ cyan: "#06989a",
+ white: "#d3d7cf",
+ brightBlack: "#555753",
+ brightRed: "#ef2929",
+ brightGreen: "#8ae234",
+ brightYellow: "#fce94f",
+ brightBlue: "#729fcf",
+ brightMagenta: "#ad7fa8",
+ brightCyan: "#34e2e2",
+ brightWhite: "#eeeeec"
+ }
+ }
+});
diff --git a/src/assets/xterm_config/xterm_general_options.js b/src/assets/xterm_config/xterm_general_options.js
new file mode 100644
index 0000000..c772c27
--- /dev/null
+++ b/src/assets/xterm_config/xterm_general_options.js
@@ -0,0 +1,107 @@
+window.inflateOptions([
+ {
+ type: "text",
+ name: "Font Family",
+ description: "The font family for terminal text.",
+ path: ["xterm", "fontFamily"],
+ },
+ {
+ type: "number",
+ name: "Font Size",
+ description: "The font size in CSS pixels for terminal text.",
+ path: ["xterm", "fontSize"],
+ min: 4,
+ },
+ {
+ type: "enum",
+ name: "Regular Font Weight",
+ description: "The font weight for non-bold text.",
+ path: ["xterm", "fontWeight"],
+ enum: ["normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"],
+ },
+ {
+ type: "enum",
+ name: "Bold Font Weight",
+ description: "The font weight for bold text.",
+ path: ["xterm", "fontWeightBold"],
+ enum: ["normal", "bold", "100", "200", "300", "400", "500", "600", "700", "800", "900"],
+ },
+ {
+ type: "boolean",
+ name: "Fit Terminal",
+ description: "Automatically fits the terminal to the page, overriding terminal columns and rows.",
+ path: ["wettyFitTerminal"],
+ },
+ {
+ type: "number",
+ name: "Terminal Columns",
+ description: "The number of columns in the terminal. Overridden by the Fit Terminal option.",
+ path: ["xterm", "cols"],
+ nullable: true,
+ },
+ {
+ type: "number",
+ name: "Terminal Rows",
+ description: "The number of rows in the terminal. Overridden by the Fit Terminal option.",
+ path: ["xterm", "rows"],
+ nullable: true,
+ },
+ {
+ type: "enum",
+ name: "Cursor Style",
+ description: "The style of the cursor",
+ path: ["xterm", "cursorStyle"],
+ enum: ["block", "underline", "bar"],
+ },
+ {
+ type: "boolean",
+ name: "Blinking Cursor",
+ description: "Whether the cursor blinks",
+ path: ["xterm", "cursorBlink"],
+ },
+ {
+ type: "number",
+ name: "Bar Cursor Width",
+ description: "The width of the cursor in CSS pixels. Only applies when Cursor Style is set to 'bar'.",
+ path: ["xterm", "cursorWidth"],
+ },
+ {
+ type: "boolean",
+ name: "Draw Bold Text In Bright Colors",
+ description: "Whether to draw bold text in bright colors",
+ path: ["xterm", "drawBoldTextInBrightColors"],
+ },
+ {
+ type: "number",
+ name: "Scroll Sensitivity",
+ 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"],
+ },
+]);
diff --git a/src/client/shared/elements.ts b/src/client/shared/elements.ts
index e54a1cf..88c7ec9 100644
--- a/src/client/shared/elements.ts
+++ b/src/client/shared/elements.ts
@@ -2,4 +2,4 @@ export const overlay = document.getElementById('overlay');
export const terminal = document.getElementById('terminal');
export const editor = document.querySelector(
'#options .editor',
-) as HTMLInputElement;
+) as HTMLIFrameElement;
diff --git a/src/client/wetty/term.ts b/src/client/wetty/term.ts
index 4bdf4b5..b98d89d 100644
--- a/src/client/wetty/term.ts
+++ b/src/client/wetty/term.ts
@@ -5,23 +5,25 @@ import { FitAddon } from 'xterm-addon-fit';
import { Terminal } from 'xterm';
import type { Term } from './shared/type';
-import { configureTerm } from './term/confiruragtion.js';
+import { configureTerm, shouldFitTerm } from './term/confiruragtion.js';
import { terminal as termElement } from '../shared/elements.js';
export function terminal(socket: typeof Socket): Term | undefined {
const term = new Terminal() as Term;
- if (_.isNull(termElement)) return;
+ if (_.isNull(termElement)) return undefined;
const webLinksAddon = new WebLinksAddon();
term.loadAddon(webLinksAddon);
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(termElement);
term.resizeTerm = () => {
- fitAddon.fit();
+ term.refresh(0, term.rows - 1);
+ if (shouldFitTerm()) fitAddon.fit();
socket.emit('resize', { cols: term.cols, rows: term.rows });
};
configureTerm(term);
window.onresize = term.resizeTerm;
+ (window as any).wetty_term = term;
return term;
}
diff --git a/src/client/wetty/term/confiruragtion.ts b/src/client/wetty/term/confiruragtion.ts
index 9c0c6e2..21bad98 100644
--- a/src/client/wetty/term/confiruragtion.ts
+++ b/src/client/wetty/term/confiruragtion.ts
@@ -2,28 +2,33 @@ import _ from 'lodash';
import type { Term } from '../shared/type';
import { copySelected, copyShortcut } from './confiruragtion/clipboard';
-import { onInput } from './confiruragtion/editor';
+import { onInput, setOptions } from './confiruragtion/editor';
import { editor } from '../../shared/elements';
import { loadOptions } from './confiruragtion/load';
export function configureTerm(term: Term): void {
- const options = loadOptions();
- Object.entries(options).forEach(([key, value]) => {
- term.setOption(key, value);
- });
- const config = JSON.stringify(options, null, 2);
- if (!_.isNull(editor)) {
- editor.value = config;
- editor.addEventListener('keyup', onInput(term));
- const toggle = document.querySelector('#options .toggler');
- const optionsElem = document.getElementById('options');
- if (!_.isNull(toggle) && !_.isNull(optionsElem)) {
- toggle.addEventListener('click', e => {
- optionsElem.classList.toggle('opened');
- e.preventDefault();
- });
- }
+ let options = loadOptions();
+ // Convert old options to new options
+ if (!("xterm" in options)) options = { xterm: options };
+ try { setOptions(term, options); } catch { /* Do nothing */ };
+
+ const toggle = document.querySelector('#options .toggler');
+ const optionsElem = document.getElementById('options');
+ if (editor == null || toggle == null || optionsElem == null) throw new Error("Couldn't initialize configuration menu");
+
+ function editorOnLoad() {
+ (editor.contentWindow as any).loadOptions(loadOptions());
+ (editor.contentWindow as any).wetty_close_config = () => { 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();
+ editor.addEventListener("load", editorOnLoad);
+
+ toggle.addEventListener('click', e => {
+ (editor.contentWindow as any).loadOptions(loadOptions());
+ optionsElem.classList.toggle('opened');
+ e.preventDefault();
+ });
term.attachCustomKeyEventHandler(copyShortcut);
@@ -35,3 +40,7 @@ export function configureTerm(term: Term): void {
false,
);
}
+
+export function shouldFitTerm(): boolean {
+ return (loadOptions() as any).wettyFitTerminal ?? true;
+}
diff --git a/src/client/wetty/term/confiruragtion/editor.ts b/src/client/wetty/term/confiruragtion/editor.ts
index 95ce885..0292cc0 100644
--- a/src/client/wetty/term/confiruragtion/editor.ts
+++ b/src/client/wetty/term/confiruragtion/editor.ts
@@ -1,23 +1,26 @@
-import JSON5 from 'json5';
-
import type { Term } from '../../shared/type';
import { editor } from '../../../shared/elements';
-export const onInput = (term: Term) => (): void => {
+export const onInput = (term: Term, updated: any) => {
try {
- const updated = JSON5.parse(editor.value);
const updatedConf = JSON.stringify(updated, null, 2);
if (localStorage.options === updatedConf) return;
- Object.keys(updated).forEach(key => {
- const value = updated[key];
- term.setOption(key, value);
- });
+ setOptions(term, updated);
+ if (!updated.wettyFitTerminal && updated.xterm.cols != null && updated.xterm.rows != null) term.resize(updated.xterm.cols, updated.xterm.rows);
term.resizeTerm();
- editor.value = updatedConf;
editor.classList.remove('error');
localStorage.options = updatedConf;
- } catch {
- // skip
+ } catch (e) {
+ console.error("Configuration Error");
+ console.error(e);
editor.classList.add('error');
}
};
+
+export function setOptions(term: Term, options: any) {
+ Object.keys(options.xterm).forEach(key => {
+ if (key === "cols" || key === "rows") return;
+ const value = options.xterm[key];
+ term.setOption(key, value);
+ });
+}
diff --git a/src/client/wetty/term/confiruragtion/load.ts b/src/client/wetty/term/confiruragtion/load.ts
index ecfcae6..1961dd3 100644
--- a/src/client/wetty/term/confiruragtion/load.ts
+++ b/src/client/wetty/term/confiruragtion/load.ts
@@ -1,7 +1,7 @@
import _ from 'lodash';
export function loadOptions(): object {
- const defaultOptions = { fontSize: 14 };
+ const defaultOptions = { xterm: { fontSize: 14 } };
try {
return _.isUndefined(localStorage.options)
? defaultOptions
diff --git a/src/server/socketServer/html.ts b/src/server/socketServer/html.ts
index ccdf72d..78a2926 100644
--- a/src/server/socketServer/html.ts
+++ b/src/server/socketServer/html.ts
@@ -9,6 +9,7 @@ const render = (
favicon: string,
css: string[],
js: string[],
+ configUrl: string,
): string => `
@@ -31,7 +32,7 @@ const render = (
href="#"
alt="Toggle options"
>
-
+
${js
@@ -50,6 +51,7 @@ export const html = (base: string, title: string): RequestHandler => (
`${base}/favicon.ico`,
cssFiles.map(css => `${base}/assets/css/${css}.css`),
jsFiles.map(js => `${base}/client/${js}.js`),
+ `${base}/assets/xterm_config/index.html`,
),
);
};