+
+
+
+
+
+
diff --git a/src/main/js/Main.js b/src/main/js/Main.js
new file mode 100644
index 0000000..39e0180
--- /dev/null
+++ b/src/main/js/Main.js
@@ -0,0 +1,174 @@
+import {$, doAfterLoad, footer, header, nav, showPage} from "@fwdekker/template";
+import katex from "katex";
+import "katex/dist/katex.min.css";
+
+
+/**
+ * A fraction that can be simplified.
+ */
+class Fraction {
+ constructor(numerator, denominator) {
+ if (!isInt(numerator) || !isInt(denominator))
+ throw new Error("Numerator and denominator must be integer-like.");
+
+ this.sign = numerator < 0 !== denominator < 0 ? -1 : 1;
+ this.numerator = Math.abs(+numerator);
+ this.denominator = Math.abs(+denominator);
+ }
+
+
+ /**
+ * Returns a new fraction such that the gcd of the numerator and denominator is 1.
+ *
+ * @returns {Fraction} a new fraction such that the gcd of the numerator and denominator is 1
+ */
+ simplify() {
+ const common = gcd(this.numerator, this.denominator);
+ return new Fraction(this.sign * this.numerator / common, this.denominator / common);
+ }
+
+ /**
+ * Returns the LaTeX string representation of this fraction.
+ *
+ * @returns {string} the LaTeX string representation of this fraction
+ */
+ toString() {
+ let frac = `\\frac{${this.numerator}}{${this.denominator}}`;
+ if (this.sign === -1)
+ frac = `-${frac}`;
+
+ return frac;
+ }
+
+ /**
+ * Returns the LaTeX string representation of this fraction, or of the numerator if the denominator is 1.
+ *
+ * @returns {string} the LaTeX string representation of this fraction, or of the numerator if the denominator
+ * is 1.
+ */
+ toReducedString() {
+ if (this.numerator === 0)
+ return "0";
+
+ let frac;
+ if (this.denominator === 1)
+ frac = `${this.numerator}`;
+ else
+ frac = `\\frac{${this.numerator}}{${this.denominator}}`;
+
+ if (this.sign === -1)
+ frac = `-${frac}`;
+
+ return frac;
+ }
+}
+
+
+// noinspection EqualityComparisonWithCoercionJS
+/**
+ * Returns `true` if and only if `n` is an integer.
+ *
+ * @param n {*} the value to check for integerness
+ * @returns {boolean} `true` if and only if `n` is an integer
+ */
+const isInt = n => n == parseInt(n);
+
+/**
+ * Returns the greatest common divisor of `a` and `b`.
+ *
+ * @param a {number} the first operand
+ * @param b {number} the second operand
+ * @returns {number} the greatest common divisor of `a` and `b`
+ */
+const gcd = (a, b) => {
+ if (b > a) {
+ const temp = a;
+ a = b;
+ b = temp;
+ }
+
+ while (true) {
+ if (b === 0) return a;
+ a %= b;
+
+ if (a === 0) return b;
+ b %= a;
+ }
+};
+
+
+doAfterLoad(() => {
+ $("#nav").appendChild(nav("/Tools/Simplify Fractions/"));
+ $("#header").appendChild(header({
+ title: "Simplify Fractions",
+ description: "Simple web tool for simplifying fractions"
+ }));
+ $("#footer").appendChild(footer({
+ author: "Felix W. Dekker",
+ authorURL: "https://fwdekker.com/",
+ license: "MIT License",
+ licenseURL: "https://git.fwdekker.com/FWDekker/simplify-fractions/src/branch/master/LICENSE",
+ vcs: "git",
+ vcsURL: "https://git.fwdekker.com/FWDekker/simplify-fractions/",
+ version: "v%%VERSION_NUMBER%%"
+ }));
+ showPage();
+});
+
+doAfterLoad(() => {
+ const numeratorInput = $("#numerator");
+ const denominatorInput = $("#denominator");
+ const outputField = $("#out");
+
+
+ /**
+ * Returns `undefined` if the inputs are valid, or a tuple consisting of the invalid element and an explanation
+ * of its invalidity otherwise.
+ *
+ * @param numerator {string} the numerator value
+ * @param denominator {string} the denominator value
+ * @returns {(HTMLElement|string)[]|undefined} `undefined` if the inputs are valid, or a tuple consisting of the
+ * invalid element and an explanation of its invalidity otherwise
+ */
+ const validateInputs = (numerator, denominator) => {
+ if (numerator === "")
+ return [numeratorInput, ""];
+ if (denominator === "")
+ return [denominatorInput, ""];
+ if (!isInt(numerator))
+ return [numeratorInput, "Numerator must be an integer."];
+ if (!isInt(denominator))
+ return [denominatorInput, "Denominator must be an integer."];
+ if (+denominator === 0)
+ return [denominatorInput, "Denominator must not be 0."];
+
+ return undefined;
+ };
+
+ /**
+ * Reads the inputs and tries to output the simplified fraction.
+ */
+ const outputSimplifiedFraction = () => {
+ let numerator = numeratorInput.value;
+ let denominator = denominatorInput.value;
+
+ const validationInfo = validateInputs(numerator, denominator);
+ if (validationInfo !== undefined) {
+ outputField.innerText = validationInfo[1];
+ return;
+ }
+
+ const fraction = new Fraction(numeratorInput.value, denominatorInput.value);
+ outputField.innerHTML = katex.renderToString(
+ fraction.toString() + " = " + fraction.simplify().toReducedString(),
+ {
+ displayMode: true,
+ throwOnError: false
+ }
+ );
+ };
+
+
+ numeratorInput.addEventListener("input", () => outputSimplifiedFraction());
+ denominatorInput.addEventListener("input", () => outputSimplifiedFraction());
+});