diff options
author | Hugo Hörnquist <hugo@lysator.liu.se> | 2023-09-13 00:01:28 +0200 |
---|---|---|
committer | Hugo Hörnquist <hugo@lysator.liu.se> | 2023-09-13 00:01:28 +0200 |
commit | a82b6c772089aa46e30c6c89ef48f514294df3cb (patch) | |
tree | e25d9b6fd1fefe8b6ac293a5c0b53293872a8f54 /static/ts/lib.ts | |
parent | Add basic documentation for lens. (diff) | |
parent | Even more documentation. (diff) | |
download | calp-a82b6c772089aa46e30c6c89ef48f514294df3cb.tar.gz calp-a82b6c772089aa46e30c6c89ef48f514294df3cb.tar.xz |
Merge branch 'next' into datarewrite-structures
Diffstat (limited to '')
-rw-r--r-- | static/ts/lib.ts (renamed from static/lib.ts) | 188 |
1 files changed, 177 insertions, 11 deletions
diff --git a/static/lib.ts b/static/ts/lib.ts index 2ef5b596..d503ac5d 100644 --- a/static/lib.ts +++ b/static/ts/lib.ts @@ -1,48 +1,112 @@ +/** + General procedures which in theory could be used anywhere. + + Besides exporting the mentioned functions, this module also + extends some base classes. + + @module + */ export { makeElement, date_to_percent, parseDate, gensym, to_local, to_boolean, - asList, round_time + asList, round_time, + format_date, } /* - General procedures which in theory could be used anywhere. - */ - -/* * https://www.typescriptlang.org/docs/handbook/declaration-merging.html */ declare global { interface Object { + /** + Introduce a format method on ALL objects + + The default implementation simply stringifies the object, but this + allows any component to overwrite it, allowing for generic custom + formatting of data. + + This also means that the format string is ignored for the default + implementation. + + See `Data.prototype.format`. + */ format: (fmt: string) => string } + /** HTMLElement extensions */ interface HTMLElement { + /** + "Private" property, storing the "true" add event listener. The + exposed addEventListener is later overwritten to also store a list of + which event listeners are added. + */ _addEventListener: (name: string, proc: (e: Event) => void) => void - listeners: Map<string, ((e: Event) => void)[]> + + /** + Contains all listeners added through `addEventListener`. + + The keys are the same as to `addEventListener` ('load', 'mouseover', + ...) + + Values are simply a list of all added listeners, probably in addition + order. + */ + listeners: Map<string, ((e: Event) => void)[]>; + + /** + Returns listeners. + + TODO why does this exist? + */ getListeners: () => Map<string, ((e: Event) => void)[]> } interface Date { + /** + A proper date formatter for javascript. + + See {@ref format_date} for details + */ format: (fmt: string) => string + + /** Indicates if the date object is in UTC or local time. */ utc: boolean + + /** + Indicates that the object only contains a date component. + + This means that any time is ignored in most operations. + */ dateonly: boolean // type: 'date' | 'date-time' } interface DOMTokenList { + /** + Searches a DOMTokenList for anything matching. + + DOMTokenLists are returned by `.classList` and similar. + + @return Returns the matching index, and the matched value, + or `undefined` if nothing was found. + */ find: (regex: string) => [number, string] | undefined } interface HTMLCollection { + /** Adds an iterator to HTMLCollections */ forEach: (proc: ((el: Element) => void)) => void } interface HTMLCollectionOf<T> { + /** Adds an iterator to HTMLCollections */ forEach: (proc: ((el: T) => void)) => void } } +/** See interface above */ HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener; +/** See interface above */ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) { if (!this.listeners) this.listeners = new Map if (!this.listeners.get(name)) this.listeners.set(name, []); @@ -50,6 +114,7 @@ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) this.listeners.get(name)!.push(proc); return this._addEventListener(name, proc); }; +/** See interface above */ HTMLElement.prototype.getListeners = function() { return this.listeners; } @@ -70,6 +135,13 @@ HTMLElement.prototype.getListeners = function() { century, due to how javascript works (...). */ +/** + * Takes a string `str`, which should be in ISO-8601 date-format, and + * returns a javascript Date object. + * Handles date-times, with and without seconds, trailing `Z' for + * time-zones, and dates without times. + * If no time is given the `dateonly` attribute is set to yes. + */ function parseDate(str: string): Date { let year: number; let month: number; @@ -112,6 +184,11 @@ function parseDate(str: string): Date { return date; } +/** + * Returns a Date object (which may be new) which is guaranteed in local time. + * This means that the `utc` field is `false`, and that + * `to_local(current_time())` should show what your wall-clock shows. + */ function to_local(date: Date): Date { if (!date.utc) return date; @@ -120,6 +197,27 @@ function to_local(date: Date): Date { /* -------------------------------------------------- */ +/** + * Creates a new DOM element of type `name`, with all keys in + * `attr` transfered to it. For example, the equivalent of + + * ```html + * <input type='number'/> + * ``` + + * would be + + * ```js + * values.push(makeElement('input', { + * type: 'number', + * })); + * ``` + * + * @param name HTML tagname + * @param attr Attributes which will be set on the created element. + * @param actualAttr Attributes which will be set on the created element, + * but through `el.setAttribute` instead of `el[key] =`... + */ function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement { let element: HTMLElement = document.createElement(name); for (let [key, value] of Object.entries(attr)) { @@ -131,6 +229,21 @@ function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement { return element; } +/** + Round clock time to closest interval. + + @param time + The desired clock-time, in decimal time. So 12:30 would be given as 12.30. + + @param fraction + The time interval to round to. To round to nearest half hour, give 0.5. + + @example + ```js + > round_time(10.1, 15/60) + 10 + ``` + */ function round_time(time: number, fraction: number): number { let scale = 1 / fraction; return Math.round(time * scale) / scale; @@ -142,14 +255,26 @@ function round_time(time: number, fraction: number): number { Just doing (new Date()/(86400*1000)) would be nice, but there's no good way to get the time in the current day. */ +/** + * Retuns how far along the date specified by `date` is, between 0 + * and 100, where 00:00 maps to 0, and 23:59 to ~100. + */ function date_to_percent(date: Date): number /* in 0, 100 */ { return (date.getHours() + (date.getMinutes() / 60)) * 100 / 24; } /* if only there was such a thing as a let to wrap around my lambda... */ /* js infix to not collide with stuff generated backend */ +/** + * Generates a new string which is (hopefully) globally unique. + * Compare with `gensym` from Lisp. + */ const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0) +/** + * Ensures that `thing` is a list. Returning it outright if it + * already is one, otherwise wrapping it in a list. +*/ function asList<T>(thing: Array<T> | T): Array<T> { if (thing instanceof Array) { return thing; @@ -159,6 +284,16 @@ function asList<T>(thing: Array<T> | T): Array<T> { } +/** + Smartly converts a value into a boolean. + + Booleans are returned as if, + + Strings are parsed, mapping `'true'` onto `true`, `'false'` onto `false`, + empty strings onto `false`, and anything else onto `true`. + + Anything else is left onto JavaScript to coerce a boolean. + */ function to_boolean(value: any): boolean { switch (typeof value) { case 'string': @@ -182,12 +317,43 @@ function datepad(thing: number | string, width = 2): string { return (thing + "").padStart(width, "0"); } -function format_date(date: Date, str: string): string { +/** + Format a date into a string. + + @param date + The datetime to format + + @param format + How the date should be converted into a string. + + The format is similar to `strftime`, but with tilde (`~`) characters + instead of percent signs, to match how Scheme does it. Valid format + specifiers are: + + | Fmt | Output | Width¹ | + |------|----------------------------------|--------| + | `~~` | Literal '~' | | + | `~Y` | Year | 4 | + | `~m` | Month number | 2 | + | `~d` | Day of month | 2 | + | `~H` | Hour | 2 | + | `~M` | Minute | 2 | + | `~S` | Second | 2 | + | `~Z` | 'Z' if date is UTC, otherwise '' | | + | `~L` | Converts date to local time² | | + + - ¹ These fields will be left padded with zeroes to that width + - ² This forces the output to be in local time, possibly converting + timezone if needed. It then outputs nothing. + See {@link to_local `to_local`} for details. + +*/ +function format_date(date: Date, format: string): string { let fmtmode = false; let outstr = ""; - for (var i = 0; i < str.length; i++) { + for (var i = 0; i < format.length; i++) { if (fmtmode) { - switch (str[i]) { + switch (format[i]) { /* Moves the date into local time. */ case 'L': date = to_local(date); break; case 'Y': outstr += datepad(date.getFullYear(), 4); break; @@ -199,10 +365,10 @@ function format_date(date: Date, str: string): string { case 'Z': if (date.utc) outstr += 'Z'; break; } fmtmode = false; - } else if (str[i] == '~') { + } else if (format[i] == '~') { fmtmode = true; } else { - outstr += str[i]; + outstr += format[i]; } } return outstr; |