-
-
Save wlkns/06bf1fb9fa7867c063dd7f72ac95d794 to your computer and use it in GitHub Desktop.
Utility functions
This file contains hidden or 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
/* A list of useful functions snippets */ | |
/** | |
* Create a GUID | |
* @see https://stackoverflow.com/a/2117523/319711 | |
* | |
* @returns RFC4122 version 4 compliant GUID | |
*/ | |
export const uuid4 = () => { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | |
// tslint:disable-next-line:no-bitwise | |
const r = (Math.random() * 16) | 0; | |
// tslint:disable-next-line:no-bitwise | |
const v = c === 'x' ? r : (r & 0x3) | 0x8; | |
return v.toString(16); | |
}); | |
}; | |
/** Create a reasonably unique ID */ | |
export const uniqueId = () => "id" + Math.random().toString(16).slice(2); | |
/** | |
* Retreive a value from an object using a dynamic path. | |
* If the attribute does not exist, return undefined. | |
* @param o: object | |
* @param s: path, e.g. a.b[0].c | |
* @see https://stackoverflow.com/a/6491621/319711 | |
*/ | |
const getPath = (obj: Record<string, any>, s: string) => { | |
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties | |
s = s.replace(/^\./, ''); // strip a leading dot | |
const a = s.split('.'); | |
let o = { ...obj }; | |
for (let i = 0, n = a.length; i < n; ++i) { | |
const k = a[i]; | |
if (k in o) { | |
o = o[k]; | |
} else if (o instanceof Array) { | |
const id = obj[k] || k; | |
const m = /([A-Z]\w+)/.exec(k); // categoryId => match Id, myNameLabel => NameLabel | |
const key = (m && m[0][0].toLowerCase() + m[0].substr(1)) || k; // key = id or nameLabel | |
const found = o.filter((i) => i[key] === id).shift(); | |
if (found) { | |
o = found; | |
} else { | |
return undefined; | |
} | |
} else { | |
return undefined; | |
} | |
} | |
return o as any; | |
}; | |
/** | |
* Get a color that is clearly visible against a background color | |
* @param backgroundColor Background color, e.g. #99AABB | |
* @returns | |
*/ | |
export const getTextColorFromBackground = (backgroundColor?: string) => { | |
if (!backgroundColor) return 'black-text'; | |
const c = backgroundColor.substring(1); // strip # | |
const rgb = parseInt(c, 16); // convert rrggbb to decimal | |
const r = (rgb >> 16) & 0xff; // extract red | |
const g = (rgb >> 8) & 0xff; // extract green | |
const b = (rgb >> 0) & 0xff; // extract blue | |
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709 | |
return luma < 105 ? 'white-text' : 'black-text'; | |
}; | |
const flatten = <T>(arr: T[]) => | |
arr.reduce((acc, cur) => (cur instanceof Array ? [...acc, ...cur] : [...acc, cur]), [] as T[]); | |
/** | |
* Debounce function wrapper, i.e. between consecutive calls of the wrapped function, | |
* there will be at least TIMEOUT milliseconds. | |
* @param func Function to execute | |
* @param timeout Timeout in milliseconds | |
* @returns | |
*/ | |
export const debounce = (func: (...args: any) => void, timeout: number) => { | |
let timer: number; | |
return (...args: any) => { | |
clearTimeout(timer); | |
timer = window.setTimeout(() => { | |
func(...args); | |
}, timeout); | |
}; | |
}; | |
/** | |
* Generate a sequence of numbers between from and to with step size: [from, to]. | |
* | |
* @static | |
* @param {number} from | |
* @param {number} to : inclusive | |
* @param {number} [count=to-from+1] | |
* @param {number} [step=1] | |
* @returns | |
*/ | |
export const range = ( | |
from: number, | |
to: number, | |
count: number = to - from + 1, | |
step: number = 1 | |
) => { | |
// See here: http://stackoverflow.com/questions/3746725/create-a-javascript-array-containing-1-n | |
// let a = Array.apply(null, {length: n}).map(Function.call, Math.random); | |
const a: number[] = new Array(count); | |
const min = from; | |
const max = to - (count - 1) * step; | |
const theRange = max - min; | |
const x0 = Math.round(from + theRange * Math.random()); | |
for (let i = 0; i < count; i++) { | |
a[i] = x0 + i * step; | |
} | |
return a; | |
}; | |
/* TEXT UTILITIES */ | |
/** | |
* Pad the string with padding character. | |
* @param str Input string or number | |
* @param length Desired length, @default 2 | |
* @param padding Padding character, @default '0' | |
*/ | |
export const padLeft = (str: string | number, length = 2, padding = '0'): string => str.toString().length >= length ? str.toString() : padLeft(padding + str, length, padding); | |
const supRegex = /\^([^_ ]+)(_|$|\s)/g; | |
const subRegex = /\_([^\^ ]+)(\^|$|\s)/g; | |
/** Expand markdown notation by converting A_1 to subscript and x^2 to superscript. */ | |
export const subSup = (s: string) => | |
s ? s.replace(supRegex, `<sup>$1</sup>`).replace(subRegex, `<sub>$1</sub>`) : s; | |
/** Remove special characters (e.g. when searching for a text) */ | |
export const cleanUpSpecialChars = (str: string) => | |
str | |
.replace(/[ÀÁÂÃÄÅàáâãäå]/g, 'a') | |
.replace(/[ÈÉÊËèéêë]/g, 'e') | |
.replace(/[ÒÓÔÖòóôö]/g, 'o') | |
.replace(/[ÌÍÎÏìíîï]/g, 'i') | |
.replace(/[ÙÚÛÜùúûü]/g, 'u') | |
.replace(/[ç]/g, 'c') | |
.replace(/[ș]/g, 's') | |
.replace(/[ț]/g, 't') | |
.replace(/[\/\-]|&|\s+/g, ' ') | |
.replace(/[^a-z0-9 ]/gi, ''); // final clean up | |
/** Join a list of items with a comma, and use AND for the last item in the list. */ | |
export const joinListWithAnd = (arr: string[] = [], and = 'and', prefix = '') => | |
arr.length === 0 | |
? '' | |
: prefix + | |
(arr.length === 1 | |
? arr[0] | |
: `${arr.slice(0, arr.length - 1).join(', ')} ${and} ${arr[arr.length - 1]}`); | |
/** Create a hash for use in keys */ | |
export const hash = (s: string | { [key: string]: any }) => { | |
if (typeof s !== 'string') { | |
s = JSON.stringify(s); | |
} | |
let hash = 0; | |
if (s.length === 0) { | |
return hash; | |
} | |
for (var i = 0; i < s.length; i++) { | |
var char = s.charCodeAt(i); | |
hash = (hash << 5) - hash + char; | |
hash = hash & hash; // Convert to 32bit integer | |
} | |
return hash; | |
}; | |
/* DATE UTILITIES */ | |
/** Format the date YYYY-MM-DD */ | |
export const formatDate = (date: number | Date = new Date()) => { | |
const d = new Date(date); | |
return `${d.getFullYear()}-${padLeft(d.getMonth() + 1)}-${padLeft(d.getDate())}`; | |
}; | |
/** | |
* Get date of ISO week, i.e. starting on a Monday. | |
* @param week week number | |
* @param year year | |
*/ | |
export const getDateOfISOWeek = (week: number, year?: number) => { | |
const w = year ? week : week % 100; | |
if (!year) { | |
year = Math.round((week - w) / 100); | |
} | |
const simple = new Date(year, 0, 1 + (w - 1) * 7); | |
const dow = simple.getDay(); | |
const ISOweekStart = simple; | |
if (dow <= 4) { | |
ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1); | |
} else { | |
ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay()); | |
} | |
return ISOweekStart; | |
}; | |
/** | |
* Get the week number from the date | |
* @param date Date to use | |
* @returns week number | |
*/ | |
export const getWeekNumber = (date: Date) => { | |
const dayNum = date.getUTCDay() || 7; | |
date.setUTCDate(date.getUTCDate() + 4 - dayNum); | |
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); | |
return Math.ceil(((date.valueOf() - yearStart.valueOf()) / 86400000 + 1) / 7); | |
}; | |
/* RANDOMNESS */ | |
/** | |
* Create a GUID | |
* @see https://stackoverflow.com/a/2117523/319711 | |
* | |
* @returns RFC4122 version 4 compliant GUID | |
*/ | |
export const uuid4 = () => { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | |
// tslint:disable-next-line:no-bitwise | |
const r = (Math.random() * 16) | 0; | |
// tslint:disable-next-line:no-bitwise | |
const v = c === 'x' ? r : (r & 0x3) | 0x8; | |
return v.toString(16); | |
}); | |
}; | |
/** Create a reasonably unique ID */ | |
export const uniqueId = () => "id" + Math.random().toString(16).slice(2); | |
/** | |
* Returns a random integer between min (inclusive) and max (inclusive), optionally filtered. | |
* If a filter is supplied, only returns numbers that satisfy the filter. | |
* | |
* @param {number} min | |
* @param {number} max | |
* @param {Function} filter numbers that do not satisfy the condition | |
*/ | |
export const random = ( | |
min: number, | |
max: number, | |
f?: (n: number, min?: number, max?: number) => boolean | |
): number => { | |
const x = min >= max ? min : Math.floor(Math.random() * (max - min + 1)) + min; | |
return f ? (f(x, min, max) ? x : random(min, max, f)) : x; | |
}; | |
/** | |
* Draw N random (unique) numbers between from and to. | |
* | |
* @static | |
* @param {number} from | |
* @param {number} to | |
* @param {number} count | |
* @param {number[]} [existing] | |
* @param {(n: number) => boolean} [filter] Optional filter to filter out the results | |
* @returns | |
*/ | |
export const randomNumbers = ( | |
from: number, | |
to: number, | |
count: number, | |
existing: number[] = [], | |
filter?: (n: number, existing?: number[]) => boolean | |
) => { | |
if (from === to) { | |
return Array(count).fill(to); | |
} | |
if (from > to || count - 1 > to - from) { | |
throw Error('Outside range error'); | |
} | |
const result: number[] = []; | |
do { | |
const x = random(from, to); | |
if (existing.indexOf(x) < 0 && result.indexOf(x) < 0) { | |
if (!filter || filter(x, result)) { | |
result.push(x); | |
count--; | |
} else { | |
count += result.length; | |
result.length = 0; | |
} | |
} | |
} while (count > 0); | |
return result; | |
}; | |
/** | |
* Returns n random items from an array | |
*/ | |
export const randomItems = <T>(arr: T[], count = 5): T[] => | |
randomNumbers(0, arr.length - 1, count).map((i) => arr[i]); | |
/** | |
* Shuffles array in place. ES6 version | |
* @param {Array} a items An array containing the items. | |
*/ | |
export const shuffle = (a: Array<any>) => { | |
for (let i = a.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
[a[i], a[j]] = [a[j], a[i]]; | |
} | |
return a; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment