Last active
July 12, 2020 20:17
-
-
Save inad9300/22ad9be994109bad525900c6b4fb656a to your computer and use it in GitHub Desktop.
TypeScript fiddlings.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Html } from '../components/Html' | |
const context = Html('canvas').getContext('2d')! | |
export function getTextWidth(text: string, fontSize: number, fontFamily = 'system-ui, sans-serif') { | |
context.font = `${fontSize}px ${fontFamily}` | |
return context.measureText(text).width | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const msInOneDay = 24 * 60 * 60 * 1_000 | |
// Source: https://stackoverflow.com/a/6117889/2018219 | |
export function getWeekNumber(d: Date) { | |
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())) | |
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)) | |
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)) | |
const msFromYearStart = d.getTime() - yearStart.getTime() | |
const weekNo = Math.ceil((msFromYearStart / msInOneDay + 1) / 7) | |
return [weekNo, d.getUTCFullYear()] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Options available in those methods that modify the URL. | |
*/ | |
interface SearchStorageWriteOptions { | |
/** | |
* Whether to reload the page after the URL change or not. | |
* @default false | |
*/ | |
reload?: boolean | |
/** | |
* Whether to add a new history entry or replace the last one. | |
* @default 'push' | |
*/ | |
history?: 'push' | 'replace' | |
} | |
type Items = { | |
[key: string]: string | |
} | |
/** | |
* Interact with the URL's query parameters using the same interface than `LocalStorage` and | |
* `SessionStorage`. Some inspiration has also been taken from `URLSearchParams`. | |
* | |
* NOTE Index signatures have been intentionally left unimplemented. The reason is two-fold: | |
* a) A direct implementation cannot be provided as if they were regular functions or accessors, | |
* meaning the data must be stored locally in this object (and no extra options can be passed). | |
* b) The URL is a shared resource that might be updated from the outside, in whose case access by | |
* index would yell out-of-date results. | |
* In addition, the index signatures of `localStorage` and `sessionStorage` might lead to very | |
* confusing situations, since keys conflicting with methods of those objects will not be | |
* accessible through the corresponding string indexes, e.g. `localStorage.key` will point to a | |
* function, even after running `localStorage.key = '42'`. | |
*/ | |
class SearchStorage implements Storage { | |
/** | |
* Return the number of query parameters in the URL. | |
* @override | |
*/ | |
get length(): number { | |
return Object.keys(this.getItems()).length | |
} | |
/** | |
* Check if a given query parameter is present in the URL, by name. | |
*/ | |
hasItem(key: string): boolean { | |
return this.getItems().hasOwnProperty(key) | |
} | |
/** | |
* Retrieve a query parameter by its position in the URL. Not recommended. | |
* @override | |
*/ | |
key(index: number): string | null { | |
const items = this.getItems() | |
const item = Object.keys(items).map(key => items[key])[index] | |
return item === undefined ? null : item | |
} | |
/** | |
* Retrieve a query parameter by name. | |
* @override | |
*/ | |
getItem(key: string): string | null { | |
const item = this.getItems()[key] | |
return item === undefined ? null : item | |
} | |
/** | |
* Retrieve all query parameters present in the URL as a key-value map. | |
*/ | |
getItems(): Items { | |
const items: Items = {} | |
location.search | |
.substr(1) | |
.split('&') | |
.map(pair => pair.split('=')) | |
.forEach(([key, value]) => items[key] = decodeURIComponent(value)) | |
return items | |
} | |
/** | |
* Set or update the value of a query parameter. | |
* @override | |
*/ | |
setItem(key: string, value: string, options?: SearchStorageWriteOptions): void { | |
const items = this.getItems() | |
items[key] = value | |
this.setItems(items, options) | |
} | |
/** | |
* Replace all existing query parameters with a new set of them, updating the history and | |
* reloading the page according to the options provided. | |
*/ | |
setItems(items: Items, options: SearchStorageWriteOptions = {}): void { | |
const search = Object | |
.keys(items) | |
.filter(key => items[key] !== null && items[key] !== undefined) | |
.map(key => key + '=' + encodeURIComponent(items[key])) | |
.join('&') | |
const url = location.pathname + (search ? '?' + search : '') + location.hash | |
if (options.history === 'replace') | |
history.replaceState(null, document.title, url) | |
else | |
history.pushState(null, document.title, url) | |
if (options.reload === true) | |
location.reload() | |
} | |
/** | |
* Set or update the value of multiple query parameters at once. | |
*/ | |
assignItems(items: Items, options?: SearchStorageWriteOptions): void { | |
this.setItems(Object.assign(this.getItems(), items), options) | |
} | |
/** | |
* Delete a specific query parameter from the URL. | |
* @override | |
*/ | |
removeItem(key: string, options?: SearchStorageWriteOptions): void { | |
const items = this.getItems() | |
delete items[key] | |
this.setItems(items, options) | |
} | |
/** | |
* Remove all query parameters from the URL. | |
* @override | |
*/ | |
clear(options?: SearchStorageWriteOptions): void { | |
this.setItems({}, options) | |
} | |
} | |
/** | |
* The one instance of `SearchStorage` that gives access to the functions to manipulate the query | |
* string part of the URL, analogously to `localStorage` and `sessionStorage`. | |
*/ | |
export const searchStorage = new SearchStorage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const stdin = process.stdin | |
const stderr = process.stderr | |
const ansi = { | |
ctrlC: '\u0003', | |
ctrlD: '\u0004', | |
eraseLine: '\u001B[2K', | |
eraseEndLine: '\u001B[K', | |
cursorLeft: '\u001B[G', | |
cursorBackward: '\u001B[1D' | |
} | |
export async function asyncSecretPrompt(prelude: string) { | |
return secretPrompt(prelude) | |
} | |
export function secretPrompt(prelude = 'Password: '): Promise<string> { | |
return new Promise((resolve, reject) => { | |
let input = '' | |
function onData(ch: string) { | |
switch (ch) { | |
case ansi.ctrlD: | |
case '\r': | |
case '\n': | |
if (input.length > 0) { | |
stop() | |
return resolve(input.replace(/\r$/, '')) | |
} | |
return reject() | |
case ansi.ctrlC: | |
stop() | |
return reject() | |
default: | |
if (ch.charCodeAt(0) === 127) { // Backspace. | |
if (input.length > 0) { | |
input = input.substr(0, input.length - 1) | |
stderr.write(ansi.cursorBackward) | |
stderr.write(ansi.eraseEndLine) | |
} | |
} else { | |
input += ch | |
stderr.write('*'.repeat(ch.length)) | |
} | |
} | |
} | |
function stop() { | |
stderr.write('\n') | |
stdin.removeListener('data', onData) | |
stdin.setRawMode!(false) | |
stdin.pause() | |
} | |
stdin.setEncoding('utf8') | |
stderr.write(ansi.eraseLine) | |
stderr.write(ansi.cursorLeft) | |
stderr.write(prelude) | |
stdin.resume() | |
stdin.setRawMode!(true) | |
stdin.on('data', onData) | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface Pushable<T> { | |
push: (promise: Promise<T>) => void | |
} | |
/** | |
* Wait for a number of promises to be available, then resolve a new promise with their results. | |
*/ | |
function waitFor<T1>(expectedPromises: number, timeoutMs?: number): Promise<T1[]> & Pushable<T1> | |
function waitFor<T1, T2>(expectedPromises: number, timeoutMs?: number): Promise<(T1 | T2)[]> & Pushable<T1 | T2> | |
function waitFor<T1, T2, T3>(expectedPromises: number, timeoutMs?: number): Promise<(T1 | T2 | T3)[]> & Pushable<T1 | T2 | T3> | |
function waitFor<T1, T2, T3, T4>(expectedPromises: number, timeoutMs?: number): Promise<(T1 | T2 | T3 | T4)[]> & Pushable<T1 | T2 | T3 | T4> | |
function waitFor<T1, T2, T3, T4, T5>(expectedPromises: number, timeoutMs?: number): Promise<(T1 | T2 | T3 | T4 | T5)[]> & Pushable<T1 | T2 | T3 | T4 | T5> | |
function waitFor(expectedPromises: number, timeoutMs?: number) { | |
let _resolve: Function | |
let _reject: Function | |
const promise = new Promise((resolve, reject) => { | |
_resolve = resolve | |
_reject = reject | |
}) | |
if (timeoutMs) { | |
setTimeout(() => _reject(`${timeoutMs} milliseconds timeout reached waiting for ${expectedPromises} promises.`), timeoutMs) | |
} | |
const promises = [] | |
return Object.assign(promise, { | |
push: (promise: Promise<any>) => { | |
promises.push(promise) | |
if (promises.length === expectedPromises) { | |
_resolve(Promise.all(promises)) | |
} | |
} | |
}) | |
} | |
// Example. | |
const waiter = waitFor<string, string>(2) | |
waiter.push(Promise.resolve('A')) | |
waiter.push(Promise.resolve('B')) | |
waiter.then(([a, b]) => console.log('Done.', a, b)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment