Allow converting multiple values simultaneously
And document a whole load of code.
This commit is contained in:
parent
7789363797
commit
7ec5171adc
|
@ -13,4 +13,6 @@
|
|||
|
||||
#inputs textarea {
|
||||
display: block;
|
||||
width: 25em;
|
||||
height: 5em;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,10 @@
|
|||
<header class="fwd-header">
|
||||
<hgroup>
|
||||
<h1><a href=".">Converter</a></h1>
|
||||
<h2>Convert numbers to and from various bases.</h2>
|
||||
<h2>
|
||||
Convert numbers to and from various bases.
|
||||
Separate values by commas to convert multiple at the same time.
|
||||
</h2>
|
||||
</hgroup>
|
||||
</header>
|
||||
</section>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// noinspection JSUnresolvedVariable
|
||||
const {$, doAfterLoad, stringToHtml} = window.fwdekker;
|
||||
import bigInt from "big-integer"
|
||||
import bigInt from "big-integer";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -42,7 +42,17 @@ function stringReplaceAlls(str, targets, replacements) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* A numeral system, expressing a conversion from decimal to this system.
|
||||
*/
|
||||
class NumeralSystem {
|
||||
/**
|
||||
* Constructs a new numeral system.
|
||||
*
|
||||
* @param base the base
|
||||
* @param alphabet the symbols representing values in this system
|
||||
* @param caseSensitive `true` if and only if capitalization affects the value of a number in this system
|
||||
*/
|
||||
constructor(base, alphabet, caseSensitive) {
|
||||
this.base = base;
|
||||
this.alphabet = alphabet;
|
||||
|
@ -50,39 +60,87 @@ class NumeralSystem {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a decimal number to a number in this system.
|
||||
*
|
||||
* @param decimalNumber {bigInt} the decimal number to convert
|
||||
* @returns {string} the representation of `decimalNumber` in this system
|
||||
*/
|
||||
decimalToBase(decimalNumber) {
|
||||
return decimalNumber.toString(this.base, this.alphabet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts multiple decimal numbers to numbers in this system.
|
||||
*
|
||||
* @param decimalNumbers {bigInt[]} the decimal numbers to convert
|
||||
* @returns {string[]} the representations of `decimalNumbers` in this system
|
||||
*/
|
||||
decimalsToBases(decimalNumbers) {
|
||||
return decimalNumbers.map((it) => this.decimalToBase(it));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number from this system to decimal.
|
||||
*
|
||||
* @param baseString {string} the number in this system to convert
|
||||
* @returns {bigInt} the decimal representation of `baseString`
|
||||
*/
|
||||
baseToDecimal(baseString) {
|
||||
return bigInt(baseString, this.base, this.alphabet, this.caseSensitive);
|
||||
}
|
||||
|
||||
filterBaseString(baseString) {
|
||||
// Regex from https://stackoverflow.com/a/3561711/
|
||||
const alphabet = this.caseSensitive
|
||||
? this.alphabet
|
||||
: this.alphabet.toLowerCase() + this.alphabet.toUpperCase();
|
||||
const regexSafeAlphabet = alphabet.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
/**
|
||||
* Converts numbers from this system to decimal.
|
||||
*
|
||||
* @param baseStrings {string[]} the numbers in this system to convert
|
||||
* @returns {bigInt[]} the decimal representations of `baseStrings`
|
||||
*/
|
||||
basesToDecimals(baseStrings) {
|
||||
return baseStrings.map((it) => this.baseToDecimal(it));
|
||||
}
|
||||
|
||||
return baseString.replace(new RegExp(`[^${regexSafeAlphabet}]`, "g"), "");
|
||||
/**
|
||||
* Removes disallowed symbols from `baseString`.
|
||||
*
|
||||
* @param baseString {string} the string representation of a number in this system
|
||||
* @returns {string} the filtered base string
|
||||
*/
|
||||
filterBaseString(baseString) {
|
||||
const alphabet = this.caseSensitive
|
||||
? (this.alphabet + ",")
|
||||
: (this.alphabet.toLowerCase() + this.alphabet.toUpperCase() + ",");
|
||||
// Regex from https://stackoverflow.com/a/3561711/
|
||||
const alphabetRegex = alphabet.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
|
||||
return baseString.replace(new RegExp(`[^${alphabetRegex}]`, "g"), "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An input field that contains a value in some numeral system.
|
||||
*/
|
||||
class NumeralSystemInput {
|
||||
/**
|
||||
* Constructs a new numeral system input, including HTML elements.
|
||||
*
|
||||
* @param name {string} the human-readable name of the system
|
||||
* @param numeralSystem {NumeralSystem} the numeral system in which values in this input are expressed
|
||||
*/
|
||||
constructor(name, numeralSystem) {
|
||||
this.name = name;
|
||||
this.numeralSystem = numeralSystem;
|
||||
const base = this.numeralSystem.base;
|
||||
|
||||
this.label = stringToHtml(`<label for="${this.name}-input">${this.name}</label>`);
|
||||
this.label = stringToHtml(`<label for="b${base}-input">${this.name} <small>(base ${base})</small></label>`);
|
||||
|
||||
this.textarea = stringToHtml(`<textarea id="${this.name}-input" class="number-input"></textarea>`);
|
||||
this.textarea = stringToHtml(`<textarea id="b${base}-input" class="number-input"></textarea>`);
|
||||
this.textarea.oninput = () => {
|
||||
if (this.textarea.value == null || this.textarea.value === "")
|
||||
return;
|
||||
|
||||
this.textarea.value = this.numeralSystem.filterBaseString(this.textarea.value);
|
||||
updateAllInputs(this, this.numeralSystem.baseToDecimal(this.textarea.value));
|
||||
updateAllInputs(this, this.numeralSystem.basesToDecimals(this.textarea.value.split(",")));
|
||||
};
|
||||
|
||||
this.wrapper = document.createElement("article");
|
||||
|
@ -91,33 +149,55 @@ class NumeralSystemInput {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends this input's elements to `parent.`
|
||||
*
|
||||
* @param parent {HTMLElement} the element to add this input to
|
||||
*/
|
||||
addToParent(parent) {
|
||||
parent.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
update(decimalNumber) {
|
||||
this.textarea.value = this.numeralSystem.decimalToBase(decimalNumber);
|
||||
/**
|
||||
* Updates the input's value to contain this input's numeral system's representation of `decimalNumbers`.
|
||||
*
|
||||
* @param decimalNumbers {bigInt[]} the decimal numbers to represent in the input
|
||||
*/
|
||||
update(decimalNumbers) {
|
||||
this.textarea.value = this.numeralSystem.decimalsToBases(decimalNumbers).join(",");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The base-64 `NumeralSystem`.
|
||||
*/
|
||||
class Base64NumeralSystem extends NumeralSystem {
|
||||
// TODO Convert static methods to static properties once supported by Firefox
|
||||
/**
|
||||
* @returns {string} the default base-64 alphabet, including padding
|
||||
*/
|
||||
static defaultAlphabet() {
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new base 64 numeral system.
|
||||
* Constructs a new base-64 numeral system.
|
||||
*
|
||||
* @param alphabet the 64 characters to encode numbers with, and the padding character at the end
|
||||
* @param alphabet {string} the 64 characters to encode numbers with, and the padding character at the end
|
||||
*/
|
||||
constructor(alphabet) {
|
||||
super(64, alphabet, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a decimal number to base-64.
|
||||
*
|
||||
* @param decimalNumber {bigInt} the decimal number to convert to base-64
|
||||
* @returns {string} the base-64 representation of `decimalNumber`
|
||||
*/
|
||||
decimalToBase(decimalNumber) {
|
||||
const hex = decimalNumber.toString(16);
|
||||
const b64 = Array.from(hex.padStart(hex.length + hex.length % 2))
|
||||
|
@ -131,6 +211,12 @@ class Base64NumeralSystem extends NumeralSystem {
|
|||
return stringReplaceAlls(btoa(b64), Base64NumeralSystem.defaultAlphabet(), this.alphabet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a base-64 number to decimal.
|
||||
*
|
||||
* @param baseString {string} the base-64 number to convert to decimal
|
||||
* @returns {bigInt} the decimal representation of `baseString`
|
||||
*/
|
||||
baseToDecimal(baseString) {
|
||||
if (baseString.length % 4 === 1) throw new Error("Invalid input string length.");
|
||||
|
||||
|
@ -141,17 +227,28 @@ class Base64NumeralSystem extends NumeralSystem {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An input field for base-64 numbers.
|
||||
*/
|
||||
class Base64NumeralSystemInput extends NumeralSystemInput {
|
||||
/**
|
||||
* @returns {Object.<string, [string, string]>} the variants for the last two characters in the base-64 alphabet
|
||||
*/
|
||||
// TODO Convert static methods to static properties once supported by Firefox
|
||||
static dropdownOptions() {
|
||||
return {"Standard": ['+', '/'], "Filename": ['-', '_'], "IMAP": ['+', ',']};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new base-64 input, including HTML elements.
|
||||
*
|
||||
* @param name {string} the human-readable name of the system
|
||||
*/
|
||||
constructor(name) {
|
||||
super(name, new Base64NumeralSystem(Base64NumeralSystem.defaultAlphabet()));
|
||||
|
||||
this.dropdown = stringToHtml(`<select id="${this.name}-dropdown"></select>`);
|
||||
this.dropdown = stringToHtml(`<select id="${this.numeralSystem.base}-dropdown"></select>`);
|
||||
this.dropdown.onchange = () => {
|
||||
const selectedOption = Base64NumeralSystemInput.dropdownOptions()[this.dropdown.value];
|
||||
this.setLastDigits(selectedOption[0], selectedOption[1]);
|
||||
|
@ -169,18 +266,27 @@ class Base64NumeralSystemInput extends NumeralSystemInput {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the last two symbols of the alphabet.
|
||||
*
|
||||
* @param c62 the new 62nd (0-indexed) symbol of the alphabet
|
||||
* @param c63 the new 63rd (0-indexed) symbol of the alphabet
|
||||
*/
|
||||
setLastDigits(c62, c63) {
|
||||
const oc62 = this.numeralSystem.alphabet[62];
|
||||
const oc63 = this.numeralSystem.alphabet[63];
|
||||
|
||||
this.numeralSystem.alphabet =
|
||||
stringReplaceAt(stringReplaceAt(this.numeralSystem.alphabet, 62, c62), 63, c63);
|
||||
this.textarea.value =
|
||||
stringReplaceAll(stringReplaceAll(this.textarea.value, oc62, c62), oc63, c63);
|
||||
this.numeralSystem.alphabet = stringReplaceAt(stringReplaceAt(this.numeralSystem.alphabet, 62, c62), 63, c63);
|
||||
this.textarea.value = stringReplaceAll(stringReplaceAll(this.textarea.value, oc62, c62), oc63, c63);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* All the inputs to display.
|
||||
*
|
||||
* @type {NumeralSystemInput[]} all the inputs to display
|
||||
*/
|
||||
const inputs = [
|
||||
new NumeralSystemInput("Binary", new NumeralSystem(2, "01")),
|
||||
new NumeralSystemInput("Octal", new NumeralSystem(8, "01234567")),
|
||||
|
@ -198,10 +304,16 @@ const inputs = [
|
|||
),
|
||||
];
|
||||
|
||||
function updateAllInputs(source, newValue) {
|
||||
/**
|
||||
* Updates the values of all inputs to represent `newValue`.
|
||||
*
|
||||
* @param source {NumeralSystemInput} the input that triggered the update
|
||||
* @param newDecimalValues {bigInt[]} the decimal representation of the new value
|
||||
*/
|
||||
function updateAllInputs(source, newDecimalValues) {
|
||||
for (const input of inputs)
|
||||
if (input !== source)
|
||||
input.update(newValue);
|
||||
input.update(newDecimalValues);
|
||||
}
|
||||
|
||||
|
||||
|
@ -210,6 +322,6 @@ doAfterLoad(() => {
|
|||
for (const input of inputs)
|
||||
input.addToParent(form);
|
||||
|
||||
updateAllInputs(undefined, bigInt(42));
|
||||
updateAllInputs(undefined, [bigInt(42), bigInt(17)]);
|
||||
inputs[0].textarea.focus();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue