From cbcc269be607cc964fbd69d179d8a0e8b8e4bffa Mon Sep 17 00:00:00 2001 From: Joris Date: Mon, 3 Feb 2020 18:28:50 +0100 Subject: Extract dom and number utilities to external files --- src/dom.ts | 54 +++++++++++++++++++++++++++++++++++ src/main.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++ src/number.ts | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 src/dom.ts create mode 100644 src/main.ts create mode 100644 src/number.ts (limited to 'src') diff --git a/src/dom.ts b/src/dom.ts new file mode 100644 index 0000000..6b1c803 --- /dev/null +++ b/src/dom.ts @@ -0,0 +1,54 @@ +type Attribute = string | boolean | ((e: Event) => void) + +type Child = Element | string + +export function h(tag: string, attrs: {[key: string]: Attribute}, children: Array = []): Element { + let element = document.createElement(tag) + + for (let name in attrs) { + let value = attrs[name] + if (typeof value === 'boolean') { + if (value) { + element.setAttribute(name, name) + } + } else if (typeof value === 'function') { + (element as any)[name] = (e: Event) => { + (value as ((e: Event) => void))(e) + } + } else { + element.setAttribute(name, value) + } + } + + children.forEach(child => { + if (typeof child === 'string') { + element.appendChild(document.createTextNode(child)) + } else { + element.appendChild(child) + } + }) + + return element +} + +export function toggleClassName(node: Element, className: string) { + if (node.className === className) { + node.className = '' + } else { + node.className = className + } +} + +export function nodeListToArray(nodeList: NodeListOf): HTMLElement[] { + const xs: HTMLElement[] = []; + nodeList.forEach(node => xs.push(node)) + return xs +} + +export function replace(node: Node, replacement: Node) { + const parentNode = node.parentNode + + if (parentNode) { + parentNode.replaceChild(replacement, node) + } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..ad83591 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,71 @@ +import * as number from './number' +import * as dom from './dom' + +/* Set up inputs for the ingredients */ + +const itemEntries = + dom.nodeListToArray(document.querySelectorAll('.g-Recipe__Content ul > li')) + .map(itemNode => ({ tag: 'li', node: itemNode })) + +const h1 = document.querySelector('.g-Recipe__Content h1') + +if (h1 !== null) { + itemEntries.push({ tag: 'h1', node: h1 }) +} + +const inputs = setupInputs(itemEntries) + +inputs.map(input => { + input.node.oninput = e => { + if (e.target !==null) { + const parsed = number.parse((e.target as HTMLInputElement).value) + + if (parsed !== undefined && parsed.before === '' && parsed.after === '') { + const factor = parsed.number / input.number + inputs.map(input2 => { + if (input.node !== input2.node) { + input2.node.value = number.prettyPrint(input2.number * factor) + } + }) + } + } + } +}) + +interface InputEntry { + tag: string; + node: HTMLElement; +} + +interface InputResult { + number: number, + node: HTMLInputElement +} + +function setupInputs(xs: InputEntry[]): InputResult[] { + const res: InputResult[] = [] + + xs.forEach(x => { + const parsed = number.parse(x.node.innerText) + + if (parsed !== undefined) { + const numberNode = number.node(x.tag, parsed) + dom.replace(x.node, numberNode.node) + res.push({ + number: parsed.number, + node: numberNode.number + }) + } + }) + + return res +} + +/* Set up done marks for steps */ + +dom.nodeListToArray(document.querySelectorAll('.g-Recipe__Content ol > li')).forEach(todo => { + todo.onclick = e => { + dom.toggleClassName(todo, 'g-Recipe__Completed') + e.stopPropagation() + } +}) diff --git a/src/number.ts b/src/number.ts new file mode 100644 index 0000000..3021dc9 --- /dev/null +++ b/src/number.ts @@ -0,0 +1,91 @@ +import { h } from './dom' + +export interface Parsed { + before: string; + number: number; + after: string; +} + +export function parse(str: string): Parsed | undefined { + let start; + for (start = 0; start < str.length; start++) { + if (isDigit(str.charAt(start))) { + break + } + } + + if (start === str.length) { + return undefined + } + + // Integer part + let integerPart = ''; + let end = start; + for (; end < str.length; end++) { + const c = str.charAt(end) + + if (!isDigit(c)) { + break + } else { + integerPart += c + } + } + + // Decimal sign + if (end < str.length && (str.charAt(end) === '.' || str.charAt(end) === ',')) { + end++ + } + + // Decimal part + let decimalPart = ''; + for (; end < str.length; end++) { + const c = str.charAt(end) + + if (!isDigit(c)) { + break + } else { + decimalPart += c + } + } + + + return { + before: str.substring(0, start), + number: parseFloat(integerPart + (decimalPart !== '' ? '.' + decimalPart : '')), + after: str.substring(end, str.length) + } +} + +function isDigit(c: string) { + return c >= '0' && c <= '9' +} + +export interface Node { + node: Element; + number: HTMLInputElement; +} + +export function node(tag: string, parsedNumber: Parsed): Node { + const numberElement = h( + 'input', + { + 'class': 'g-Number', + 'value': prettyPrint(parsedNumber.number) + } + ) as HTMLInputElement + + return { + node: h(tag, {}, [parsedNumber.before, numberElement, parsedNumber.after]), + number: numberElement, + } +} + +export function prettyPrint(n: number): string { + const xs = n.toString().split('.') + + if (xs.length == 2) { + return xs[0] + ',' + xs[1].substring(0, 2) + } else { + return xs[0] + } +} -- cgit v1.2.3