2021-04-18 18:21:34 +02:00
|
|
|
// noinspection JSUnresolvedVariable
|
2022-11-21 22:27:58 +01:00
|
|
|
const {$, doAfterLoad} = window.fwdekker;
|
2020-05-16 18:54:07 +02:00
|
|
|
import bigInt from "big-integer"
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces the character at the given index with the given replacement.
|
|
|
|
*
|
|
|
|
* @param str the string to replace in
|
|
|
|
* @param index the index in the given string to replace at
|
|
|
|
* @param replacement the replacement to insert into the string
|
|
|
|
* @returns {string} the input string with one character replaced
|
|
|
|
*/
|
|
|
|
const stringReplaceAt = (str, index, replacement) =>
|
2022-11-21 22:27:58 +01:00
|
|
|
str.substring(0, index) + replacement + str.substring(index + replacement.length);
|
2020-05-16 18:54:07 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces all instances of the target with the replacement.
|
|
|
|
*
|
|
|
|
* @param str the string to replace in
|
|
|
|
* @param target the character to replace
|
|
|
|
* @param replacement the replacement to insert into the string
|
|
|
|
* @returns {string} the input string with all instances of the target replaced
|
|
|
|
*/
|
|
|
|
const stringReplaceAll = (str, target, replacement) =>
|
|
|
|
str.split(target).join(replacement);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs `stringReplaceAll` for each character in `targets` and `replacements`.
|
|
|
|
*
|
|
|
|
* @param str the string to replace in
|
|
|
|
* @param targets the characters to replace
|
|
|
|
* @param replacements the replacements to insert into the string; each character here corresponds to a character
|
|
|
|
* in the targets string
|
|
|
|
* @returns {string} the input string with all instances of the targets replaced
|
|
|
|
*/
|
|
|
|
const stringReplaceAlls = (str, targets, replacements) =>
|
|
|
|
Array.from(targets).reduce((output, target, index) =>
|
|
|
|
stringReplaceAll(output, target, replacements[index]), str);
|
|
|
|
|
|
|
|
|
|
|
|
class NumeralSystem {
|
|
|
|
constructor(base, alphabet, caseSensitive) {
|
|
|
|
this.base = base;
|
|
|
|
this.alphabet = alphabet;
|
|
|
|
this.caseSensitive = caseSensitive;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decimalToBase(decimalNumber) {
|
|
|
|
return decimalNumber.toString(this.base, this.alphabet);
|
|
|
|
}
|
|
|
|
|
|
|
|
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, "\\$&");
|
|
|
|
|
|
|
|
return baseString.replace(new RegExp(`[^${regexSafeAlphabet}]`, "g"), "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class NumeralSystemInput {
|
|
|
|
constructor(name, numeralSystem) {
|
|
|
|
this.name = name;
|
|
|
|
this.numeralSystem = numeralSystem;
|
|
|
|
|
|
|
|
this.label = document.createElement("label");
|
|
|
|
this.label.setAttribute("for", `${this.name}Input`);
|
|
|
|
this.label.innerHTML = this.name;
|
|
|
|
|
|
|
|
this.textarea = document.createElement("textarea");
|
|
|
|
this.textarea.id = `${this.name}Input`;
|
|
|
|
this.textarea.className = "numberInput";
|
|
|
|
this.textarea.oninput = () => {
|
|
|
|
if (this.textarea.value === undefined || 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));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addToParent(parent) {
|
|
|
|
parent.appendChild(this.label);
|
|
|
|
parent.appendChild(this.textarea);
|
|
|
|
}
|
|
|
|
|
|
|
|
update(decimalNumber) {
|
|
|
|
this.textarea.value = this.numeralSystem.decimalToBase(decimalNumber);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Base64NumeralSystem extends NumeralSystem {
|
|
|
|
// TODO Convert static methods to static properties once supported by Firefox
|
|
|
|
static defaultAlphabet() {
|
|
|
|
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new base 64 numeral system.
|
|
|
|
*
|
|
|
|
* @param alphabet the 64 characters to encode numbers with, and the padding character at the end
|
|
|
|
*/
|
|
|
|
constructor(alphabet) {
|
|
|
|
super(64, alphabet, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decimalToBase(decimalNumber) {
|
|
|
|
const hex = decimalNumber.toString(16);
|
|
|
|
const b64 = Array.from(hex.padStart(hex.length + hex.length % 2))
|
|
|
|
.reduce((result, value, index, array) => {
|
|
|
|
if (index % 2 === 0) result.push(array.slice(index, index + 2));
|
|
|
|
return result;
|
|
|
|
}, [])
|
|
|
|
.map(pair => String.fromCharCode(parseInt(pair.join(""), 16)))
|
|
|
|
.join("");
|
|
|
|
|
|
|
|
return stringReplaceAlls(btoa(b64), Base64NumeralSystem.defaultAlphabet(), this.alphabet);
|
|
|
|
}
|
|
|
|
|
|
|
|
baseToDecimal(baseString) {
|
|
|
|
if (baseString.length % 4 === 1) throw new Error("Invalid input string length.");
|
|
|
|
|
|
|
|
const normalBaseString = stringReplaceAlls(baseString, this.alphabet, Base64NumeralSystem.defaultAlphabet());
|
|
|
|
const hex = Array.from(atob(normalBaseString))
|
|
|
|
.map(char => char.charCodeAt(0).toString(16).padStart(2, "0")).join("");
|
|
|
|
return bigInt(hex, 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Base64NumeralSystemInput extends NumeralSystemInput {
|
|
|
|
// TODO Convert static methods to static properties once supported by Firefox
|
|
|
|
static dropdownOptions() {
|
|
|
|
return {"Standard": ['+', '/'], "Filename": ['-', '_'], "IMAP": ['+', ',']};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
constructor(name) {
|
|
|
|
super(name, new Base64NumeralSystem(Base64NumeralSystem.defaultAlphabet()));
|
|
|
|
|
|
|
|
this.dropdown = document.createElement("select");
|
2022-11-21 22:27:58 +01:00
|
|
|
this.dropdown.id = `${this.name}-dropdown`;
|
2020-05-16 18:54:07 +02:00
|
|
|
this.dropdown.onchange = () => {
|
|
|
|
const selectedOption = Base64NumeralSystemInput.dropdownOptions()[this.dropdown.value];
|
|
|
|
this.setLastDigits(selectedOption[0], selectedOption[1]);
|
|
|
|
};
|
|
|
|
|
|
|
|
this.options =
|
|
|
|
Object.keys(Base64NumeralSystemInput.dropdownOptions()).map(key => {
|
|
|
|
const option = document.createElement("option");
|
|
|
|
option.value = key;
|
|
|
|
option.text = key + ": " + Base64NumeralSystemInput.dropdownOptions()[key].join("");
|
|
|
|
return option;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
addToParent(parent) {
|
|
|
|
this.options.forEach(option => this.dropdown.appendChild(option));
|
|
|
|
|
|
|
|
parent.appendChild(this.label);
|
2022-11-21 22:27:58 +01:00
|
|
|
parent.appendChild(this.dropdown);
|
2020-05-16 18:54:07 +02:00
|
|
|
parent.appendChild(this.textarea);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const inputs = [
|
|
|
|
new NumeralSystemInput("Binary", new NumeralSystem(2, "01")),
|
|
|
|
new NumeralSystemInput("Octal", new NumeralSystem(8, "01234567")),
|
|
|
|
new NumeralSystemInput("Decimal", new NumeralSystem(10, "0123456789")),
|
|
|
|
new NumeralSystemInput("Duodecimal", new NumeralSystem(12, "0123456789ab", false)),
|
|
|
|
new NumeralSystemInput("Hexadecimal", new NumeralSystem(16, "0123456789abcdef", false)),
|
|
|
|
new Base64NumeralSystemInput("Base64"),
|
|
|
|
new NumeralSystemInput(
|
|
|
|
"ASCII",
|
|
|
|
new NumeralSystem(
|
|
|
|
256,
|
|
|
|
new Array(256).fill(0).map((_, it) => String.fromCharCode(it)).join(""),
|
|
|
|
true
|
|
|
|
)
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
const updateAllInputs = (source, newValue) => {
|
|
|
|
for (const input of inputs)
|
|
|
|
if (input !== source)
|
|
|
|
input.update(newValue);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
doAfterLoad(() => {
|
|
|
|
const inputParent = $("#inputs");
|
|
|
|
|
|
|
|
for (const input of inputs)
|
|
|
|
input.addToParent(inputParent);
|
|
|
|
|
|
|
|
updateAllInputs(undefined, bigInt(42));
|
|
|
|
inputs[0].textarea.focus();
|
2022-11-21 22:27:58 +01:00
|
|
|
|
|
|
|
$("main").classList.remove("hidden");
|
2020-05-16 18:54:07 +02:00
|
|
|
});
|