Iterables / Arrays
type IterableKind<T> = Iterable<T> | T[]; export function ensureArray<T>(data?: IterableKind<T>): T[] { if (!data) return []; if (Array.isArray(data)) return data; if (typeof data === "string") return [data]; if (data instanceof Map) return []; return Array.from(data); } export function firstItem<T>(data?: IterableKind<T>): T | undefined { return ensureArray(data)[0]; }; export function arraySum<T>(arr: IterableKind<T>, fn: (arg: T) => number): number { return ensureArray(arr).reduce((p, c) => p + fn(c), 0); } export function arrayMin<T>(arr: IterableKind<T>, fn: (arg: T) => number): number | undefined { const values = ensureArray(arr).map(fn); return values.length ? Math.min.apply(Math, values) : undefined; } export function arrayMax<T>(arr: IterableKind<T>, fn: (arg: T) => number): number | undefined { const values = ensureArray(arr).map(fn); return values.length ? Math.max.apply(Math, values) : undefined; }
import { getISOWeek, getISOWeekYear } from "date-fns"; import { IYearWeek } from "../domain/general/IYearWeek"; export const isDebugMode = `${window.location.origin}`.includes("localhost"); const eq = (a: string, b: string) => a === b; const strEq = (a?: string, b?: string) => String(a).trim().toLowerCase() === String(b).trim().toLowerCase(); const remove = (arr: any[], item: any) => { const i = arr.indexOf(item); if (i >= 0) { arr.splice(i, 1); } }; const isset = (x: any) => ![undefined, null].includes(x); const getString = (x: any) => isset(x) ? `${x}` : ""; const getFirstItem = (obj: any) => { if (isset(obj)) { return Array.from(obj)[0]; } return null; }; export type Dictionary<T> = { [id: string]: T }; const htmlEncode = (source: string) => { let i = 0; let ch: string; let peek = ""; const result: string[] = []; let line: string[] = []; // Stash the next character and advance the pointer const next = () => { peek = source.charAt(i); i += 1; }; // Start a new "line" of output, to be joined later by <br /> const endline = () => { result.push(line.join("")); line = []; }; // Push a character or its entity onto the current line const push = () => { if (ch !== "\r" && ch !== "\n" && (ch < " " || ch > "~")) { line.push(`&#${ch.charCodeAt(0)};`); } else { line.push(ch); } }; next(); while (i <= source.length) { ch = peek; next(); switch (ch) { case "<": line.push("<"); break; case ">": line.push(">"); break; case "&": line.push("&"); break; case "\"": line.push("""); break; case "'": line.push("'"); break; default: push(); } } endline(); return result.join("<br />"); }; const unique = (arr: any[], valFn: (item: any) => any) => { const values: { [key: string]: any } = {}; return arr.filter(item => { const val = String(valFn(item)); return val in values ? false : values[val] = true; }); }; const propertiesEqual = (a: any, b: any, properties: string[]) => a && b && properties.every((p: any) => a[p] === b[p]); export function arraysEqual(arr1: any[], arr2: any[]) { return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]); } export function arraysEqualUnordered(arr1: any[], arr2: any[]) { return JSON.stringify([...arr1].sort()) === JSON.stringify([...arr2].sort()); } const dateAdd = (date: Date, ms: number) => new Date(date.getTime() + ms); const getToday = () => { const d = new Date(); d.setHours(23, 59, 59); return d; }; export interface IPageInfo { from?: Date; to?: Date; page: number; } type Pager = (page: number) => IPageInfo; const historyPager = (msRange: number): Pager => { const today = getToday(); return (page: number) => { const from = dateAdd(today, -msRange * (page + 1)); from.setHours(0, 0, 0); let to: Date | undefined = dateAdd(today, -msRange * page); if (page < 1) { to = undefined; } return { from, to, page }; }; }; // only skips sunday const nextWorkDay = function (date: Date): Date { const tomorrow = new Date(date.setDate(date.getDate() + 1)); return tomorrow.getDay() % 7 ? tomorrow : nextWorkDay(tomorrow); }; // only skips sunday const lastWorkday = function (date: Date): Date { const yesterday = new Date(date.setDate(date.getDate() - 1)); return yesterday.getDay() % 7 ? yesterday : lastWorkday(yesterday); }; const isFileImage = (fileName: string) => { const extensions = [".JPG", ".JPEG", ".JPE", ".BMP", ".GIF", ".PNG"]; return extensions.some(x => fileName.toUpperCase().includes(x)); }; const isValidDate = (d: any) => d instanceof Date && !isNaN(d.getTime()); const getNumber = (x: any) => Number(String(x).replace(/([^\d])/, "").replace(/^0+/, "")); const isNumberKey = (event: any) => { const charCode = (event.which) ? event.which : event.keyCode; if (charCode !== 46 && charCode > 31 && (charCode < 48 || charCode > 57)) { return false; } return true; }; const escapeHtml = (unsafe: any): string => { return String(unsafe) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; const onChange = (objToWatch: any, fn: any) => { const handler = { set(target: any, property: any, value: any) { fn(); return Reflect.set(target, property, value); }, deleteProperty(target: any, property: any) { fn(); return Reflect.deleteProperty(target, property); } }; return new Proxy(objToWatch, handler); }; function debounced(delay: number, fn: (...args: Array<any>) => any) { let timerId: any; return function (...args: Array<any>) { if (timerId) { clearTimeout(timerId); } timerId = setTimeout(() => { fn(...args); timerId = null; }, delay); }; } const displayTextAreaProperly = (str: string): string => { if (str) { return str.replace(/\n/g, "<br>"); } return ""; }; const createCounter = () => { let c = 0; return () => { c += 1; return c; }; }; const getCircularReplacer = () => { const seen = new WeakSet(); return (_key: any, value: any) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; }; const toJson = (x: any) => JSON.stringify(x, getCircularReplacer()); const parseJson = (x: any) => { if (typeof x !== "string") { return undefined; } try { return JSON.parse(x); } catch { return undefined; } }; const getLocalStorage = <T>(key: string): T => parseJson(localStorage.getItem(key)) as T; const setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state)); export const getSessionStorage = <T>(key: string): T => parseJson(sessionStorage.getItem(key)) as T; export const setSessionStorage = (key: string, state: any): any => sessionStorage.setItem(key, toJson(state)); const c = encodeURIComponent; const cv = (v: any) => v && v.toISOString ? v.toISOString() : v; const objToQueryString = (obj: any) => Object.keys(obj).map(k => `${c(k)}=${c(cv(obj[k]))}`).join("&"); function b64toBlob(b64Data: string, contentType: string, sliceSize?: number): Blob { contentType = contentType || ""; sliceSize = sliceSize || 512; const byteCharacters = atob(b64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { const slice = byteCharacters.slice(offset, offset + sliceSize); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } return new Blob(byteArrays, { type: contentType }); } function imagedataToBlob(imagedata: ImageData): Promise<Blob> { return new Promise((resolve, reject) => { try { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); if (!ctx) { reject(); return; } canvas.width = imagedata.width; canvas.height = imagedata.height; ctx.putImageData(imagedata, 0, 0); canvas.toBlob((blob) => { if (!blob) { reject(); return; } resolve(blob); }, "image/jpeg", 0.95); } catch (error) { reject(error); } }); } const arrRemove = (arr: Array<any>, item: any) => { const i = arr.indexOf(item); if (i >= 0) { arr.splice(i, 1); } }; const filterUnique = () => { const keys = new Set(); return (key: string) => !keys.has(key) ? keys.add(key) || true : false; }; const isSet = (x: any) => x != null; // works for [null, undefined], [ false, 0, Nan ] are true const isNumber = (x: any) => typeof x === "number"; const isString = (x: any) => typeof x === "string"; const isIterable = (value: any) => Symbol.iterator in Object(value); function* iterator(iterable: Array<any>) { for (const item of iterable) { yield item; } } function* zipIterables(...iterables: Array<any>) { const iterators = iterables.map(x => iterator(x)); while (true) { const stats = []; const results = []; for (const iterable of iterators) { const result = iterable.next(); stats.push(result.done); results.push(result.value); } if (stats.every((stat) => stat === true)) { break; } yield results; } } const stringComparer = (locales?: any, options?: any) => { if (!locales) { locales = "en"; } if (!options) { options = { numeric: true, sensitivity: "base" }; } const collator = new Intl.Collator(locales, options); return (a: string, b: string) => collator.compare(a, b); }; const comparer = stringComparer(); function compare(a: any, b: any, reverse: boolean): number { const [x, y] = reverse ? [b, a] : [a, b]; if (!isSet(x) && !isSet(y)) { return 0; } if (!isSet(x) && isSet(y)) { return -1; } if (!isSet(y) && isSet(x)) { return 1; } if (isNumber(x) && isNumber(y)) { return x - y; } if (isString(x) && isString(y)) { return comparer(x, y); } if (isIterable(x) && isIterable(y)) { for (const d of Array.from(zipIterables(x, y))) { const c = compare(d[0], d[1], reverse); if (c !== 0) { return c; } } return 0; } return comparer(String(x), String(y)); } export function orderBy<T>(array: T[], fn: (x: T) => any, reverse?: boolean) { const result = Array.from(array); result.sort((a, b) => compare(fn(a), fn(b), reverse || false)); return result; } export function getDate(input: any): Date | undefined { if (typeof input === "object" && input instanceof Date) { return new Date(input); } if (typeof input === "string") { return new Date(input); } return undefined; } export function numberFormat(value: number) { if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; const currencyFormatter = new Intl.NumberFormat("nl-NL"); return currencyFormatter.format(value); } export function numberFormatRounded(value?: number, decimals: number = 0, unit: string = "") { if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; const currencyFormatter = new Intl.NumberFormat("nl-NL"); return currencyFormatter.format(round(value, decimals) ?? 0) + (unit ? ` ${unit}` : ""); } export function numberFormatRoundedWithoutSeperator(value?: number, decimals: number = 0, unit: string = "") { if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; // Use fixed-point notation to avoid thousand separators const roundedValue = (value ?? 0).toFixed(decimals); return roundedValue + (unit ? ` ${unit}` : ""); } export const normalizeKeysToLowercase = (obj: any) => { return Object.keys(obj).reduce((acc, key) => { const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1); acc[lowercaseKey] = obj[key]; return acc; }, {} as any); }; export function currencyFormat(value: number | undefined, currencyISO: string = "EUR"): string { if (value === null || value === undefined) return ""; if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; const currencyFormatter = new Intl.NumberFormat("nl-NL", { style: "currency", currency: currencyISO, minimumFractionDigits: 2, maximumFractionDigits: 6 }); return currencyFormatter.format(value); } export function round(value: number | undefined, precision: number): number | undefined { if (value === null || value === undefined) return undefined; value = Number(value) || 0.0; const multiplier = Math.pow(10, precision || 0); return Math.round(value * multiplier) / multiplier; } export function getNumberFromString(value: any): number | undefined { if (value === null || value === undefined || value === "") return undefined; const s = String(value).replace(/[^\d.,]/, ""); const parts = s.split(/[.,]/); if (parts.length > 1) parts.splice(parts.length - 1, 0, "."); // insert at index const j = parts.join(""); return parseFloat(j) || 0.0; } interface IGroup<U> { group: string; data: U; } export function groupBy<T, U>(array: T[], fn: (item: T) => IGroup<U>) { const groups: { [key: string]: U[] } = {}; array.forEach((item) => { const result = fn(item); const group = JSON.stringify(result.group); groups[group] = groups[group] || []; groups[group].push(result.data); }); return Object.keys(groups).map((group) => groups[group]); }; export const getDateXDaysAgo = (numOfDays: number, date = new Date()) => { const daysAgo = new Date(date.getTime()); daysAgo.setDate(date.getDate() - numOfDays); return daysAgo; } const nameof = <T>(name: Extract<keyof T, string>): string => name; export function propertyOf<T>(name: keyof T) { return name; } export function pushRange(arr: any[], itemsToPush: any[]) { Array.prototype.push.apply(arr, itemsToPush); } export interface IHaveParentId { id: any; parentId?: any; } interface IHasChilds { children: any[]; } export function createDataTree<T extends IHasChilds>(dataset: IHaveParentId[]) { const hashTable = new Map<any, any>(); dataset.forEach(aData => hashTable.set(aData.id, { ...aData })); const dataTree: T[] = []; dataset.forEach(aData => { if (aData.parentId) { const parent = hashTable.get(aData.parentId); parent.children ??= []; parent.children.push(hashTable.get(aData.id)); } else dataTree.push(hashTable.get(aData.id)) }); for (const v of hashTable.values()) { if (v.children && v.children.length < 1) delete v.children; } return dataTree; }; const toBase64 = (file: Blob) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); function* rangeGen(from: number, to: number) { let i = from; while (i <= to) { yield i; i++; } } export function range(from: number, to: number) { return Array.from(rangeGen(from, to)); } export function sumArray<T>(arr: T[], fn: (arg: T) => number): number { return arr.reduce((p, c) => p + fn(c), 0); } export function getCurrentWeek() { return { year: getISOWeekYear(new Date()), week: getISOWeek(new Date()) }; } export function getWeeksBack(weekCount = 4) { const now = new Date(); now.setDate(now.getDate() - 7 * weekCount); return { year: getISOWeekYear(now), week: getISOWeek(now) }; } export function SetCurrentWeekStyle(yw: IYearWeek): React.CSSProperties { const currentWeek = getCurrentWeek(); return getCurrentWeekStyle(weeksEqual(yw, currentWeek)); } export function getCurrentWeekStyle(isCurrentWeek: boolean): React.CSSProperties { if (!isCurrentWeek) return {}; return { fontWeight: "bold", color: "#5D3FD3" }; } export function isHttpSucces(status?: number) { return !!status && status >= 200 && status < 400; } export function bytesToKBMB(bytes?: number) { if (!bytes) return; if (bytes < 1024) { return bytes + " bytes"; } else if (bytes < 1024 * 1024) { return (bytes / 1024).toFixed(2) + " KB"; } else { return (bytes / (1024 * 1024)).toFixed(2) + " MB"; } } export function GetYearWeekFromKey(yearWeekString: string) { const parts = yearWeekString.split("-"); const year = parseInt(parts[0], 10); const week = parseInt(parts[1], 10); const yearWeek = { key: yearWeekString, year: year, week: week } as IYearWeek return yearWeek; } export function truncateString(text: string, maxLength: number): string { text = String(text); if (text.length <= maxLength) { return text; } return text.slice(0, maxLength - 3) + "..."; } export function limitArraySize<T>(array: T[], maxSize: number): T[] { return array.slice(0, maxSize); } export function IsNullOrEmpty(text: string) { if (text === undefined || text === null || text === "") return true; return false; } export const weeksEqual = (a: IYearWeek | undefined, b: IYearWeek | undefined) => getWeekKey(a) === getWeekKey(b); export const getWeekKey = (yw: IYearWeek | undefined) => { return `${yw?.year}-` + `${yw?.week}`.padStart(2, "0"); } export const getWeekKeyDefault = (yw: IYearWeek | undefined) => { if (!yw) return undefined; return `${yw?.year}-` + `${yw?.week}`.padStart(2, "0"); } export function isSubset(array1: string[], array2: string[]): boolean { const set2 = new Set(array2); return array1.every(element => set2.has(element)); } export const weekAsInt = (yw: IYearWeek) => (yw.year * 100) + yw.week; export const joinClasses = (...args: any[]) => Array.from(args).filter(x => !!x).join(" "); export function clearTextSelection() { const w = window as any; const d = document as any; if (w.getSelection) { if (w.getSelection().empty) { // Chrome w.getSelection().empty(); } else if (w.getSelection().removeAllRanges) { // Firefox w.getSelection().removeAllRanges(); } } else if (d.selection) { // IE? d.selection.empty(); } } export function delay(ms: number) { return new Promise((r) => setTimeout(r, ms)); } export function deserializeState<T>(state: string | null, defValue: T): T { if (!state) return defValue; try { return JSON.parse(atob(state)); } catch (error) { return defValue; } } export function serializeState<T>(state: T): string { return btoa(JSON.stringify(state)); } // Debounce function to limit the rate at which a function can fire. export function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void { let timeout: ReturnType<typeof setTimeout> | null; return function (...args: Parameters<T>) { const later = () => { if (timeout !== null) { clearTimeout(timeout); } func(...args); }; if (timeout !== null) { clearTimeout(timeout); } timeout = setTimeout(later, wait); }; } export function ensureArray(b: any): any[] { if (Array.isArray(b)) return b; if (!b) return []; return [b]; } export function ensureTypedArray<T>(b?: T[]): T[] { if (Array.isArray(b)) return b; if (!b) return []; return [b]; } export { arrRemove, b64toBlob, createCounter, dateAdd, debounced, displayTextAreaProperly, eq, escapeHtml, filterUnique, getFirstItem, getLocalStorage, getNumber, getString, getToday, historyPager, htmlEncode, imagedataToBlob, isFileImage, isNumberKey, isset, isValidDate, lastWorkday, nameof, nextWorkDay, objToQueryString, onChange, propertiesEqual, remove, setLocalStorage, strEq, toBase64, unique };
import moment from "moment"; const minimumValue = (value: number, min: number) => value < min ? min : value; const isLast = (arr: Array<any>, i: number) => arr.length - 1 === i; const nextItemInArray = (arr: Array<any>, i: number) => arr[minimumValue(i + 1, arr.length - 1)]; const unique = (arr: Array<any>) => Array.from(new Set([...arr])); export function isValidNumber(x: any) { return typeof (x) === "number" && Number.isFinite(x); } export const filterUnique = () => { const keys = new Set(); return (key: string) => !keys.has(key) ? keys.add(key) || true : false; }; const getFileExtension = (str: string) => { const splitted = str.split("."); return splitted[splitted.length - 1].toLowerCase(); }; const toBase64 = (file: Blob) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); const b64toBlob = (base64: string) => fetch(base64).then(res => res.blob()); const asyncForEach = async (array: Array<any>, callback: Function) => { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array); } }; function escapeHtml(unsafe: any): string { return String(unsafe) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } const any = (arr: Array<any>) => arr?.length > 0; const stringComparer = (locales?: any, options?: any) => { if (!locales) locales = "en"; if (!options) options = { numeric: true, sensitivity: "base" }; const collator = new Intl.Collator(locales, options); return (a: string, b: string) => collator.compare(a, b); }; const stringCompare = stringComparer(); export interface IFilterItem { text: string; value: any; } function getFilter<T>(values: T[keyof T][], fn: (x: T[keyof T]) => IFilterItem) { return Array.from(new Set(values)).map(fn); } function escapeRegExp(s: string) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string } function getPropertyValue<T, K extends keyof T>(o: T, propertyName: K): T[K] { return o[propertyName];// o[propertyName] is of type T[K] }; const createQuery = (data: any) => { const c = encodeURIComponent; const cv = (v: any) => v && v.toISOString ? v.toISOString() : v; return "?" + Object.keys(data).map(k => `${c(k)}=${c(cv(data[k]))}`).join("&"); }; const stringIsNullOrEmpty = (str: any) => { if (typeof str !== "string") return false; if (str.trim().length < 1) return false; return true; }; const getCircularReplacer = () => { const seen = new WeakSet(); return (key: any, value: any) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; }; const createGuid = (): string => { let d = new Date().getTime(); const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { const r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); // eslint-disable-next-line no-mixed-operators return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); }); return uuid; }; const toJson = (x: any) => JSON.stringify(x, getCircularReplacer()); const parseJson = (x: any) => { if (typeof x !== "string") return undefined; try { return JSON.parse(x); } catch { return undefined; } }; const getLocalStorage = <T>(key: string): T => parseJson(localStorage.getItem(key)) as T; const setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state)); const getLocationStateKey = (path: string) => ("state-" + String(path)).toLowerCase(); const getLocationState = <T>(path: string): T => parseJson(sessionStorage.getItem(getLocationStateKey(path))) as T; const setLocationState = (path: string, state: any): any => sessionStorage.setItem(getLocationStateKey(path), toJson(state)); export const toSortableDate = (x?: Date) => `${x?.toISOString()}`; const formatDate = (value: Date) => value ? moment(value).format("DD-MM-yyyy") : ""; const formatDateTime = (value: Date) => value ? moment(value).format("DD-MM-yyyy HH:mm") : ""; export function classes(...args: string[]) { return Array.from(args).filter(x => x).join(" "); } export const limit = (v: number, min: number, max: number): number => Math.max(min, Math.min(max, Number(v) || 0)); interface IGroup<U> { group: string; data: U; } export function groupBy<T, U>(array: T[], fn: (item: T) => IGroup<U>) { const groups: { [key: string]: U[] } = {}; array.forEach((item) => { const result = fn(item); const group = JSON.stringify(result.group); groups[group] = groups[group] || []; groups[group].push(result.data); }); return Object.keys(groups).map((group) => groups[group]); }; export function round2Dec(num: number) { return Math.round(num * 100) / 100; } function addPostfixIfNumber(input: string, postFix: string) { if (!isNumeric(input)) { return input; } return `${input}${postFix}`; } function isNumeric(str: any) { return !isNaN(str) && !isNaN(parseFloat(str)); } export { addPostfixIfNumber, any, asyncForEach, b64toBlob, createGuid, createQuery, escapeHtml, escapeRegExp, formatDate, formatDateTime, getFileExtension, getFilter, getLocalStorage, getLocationState, getPropertyValue, isLast, minimumValue, nextItemInArray, parseJson, setLocalStorage, setLocationState, stringCompare, stringComparer, stringIsNullOrEmpty, toBase64, toJson, unique }; // --
/* eslint-disable @typescript-eslint/no-explicit-any */ // import { getISOWeek, getISOWeekYear } from "date-fns"; // import { IYearWeek } from "../domain/general/IYearWeek"; const eq = (a: string, b: string) => a === b; const strEq = (a?: string, b?: string) => String(a).trim().toLowerCase() === String(b).trim().toLowerCase(); const remove = (arr: any[], item: any) => { const i = arr.indexOf(item); if (i >= 0) { arr.splice(i, 1); } }; const isset = (x: any) => ![undefined, null].includes(x); const getString = (x: any) => isset(x) ? `${x}` : ""; const getFirstItem = (obj: any) => { if (isset(obj)) { return Array.from(obj)[0]; } return null; }; export const roundNumberUpBy = (n: number, roundBy: number) => Math.ceil(n * 1.0 / roundBy) * roundBy; // Only add up export const roundNumberBy = (n: number, roundBy: number) => Math.round(n * 1.0 / roundBy) * roundBy; export const roundNumber = (n: number) => roundNumberBy(n, 1); export function isDigitsOnly(str: string): boolean { return /^\d+$/.test(str); } export function getArray<T>(data?: T[]) { return Array.from(data || []); } export type Dictionary<T> = { [id: string]: T }; const htmlEncode = (source: string) => { let i = 0; let ch: string; let peek = ""; const result: string[] = []; let line: string[] = []; // Stash the next character and advance the pointer const next = () => { peek = source.charAt(i); i += 1; }; // Start a new "line" of output, to be joined later by <br /> const endline = () => { result.push(line.join("")); line = []; }; // Push a character or its entity onto the current line const push = () => { if (ch !== "\r" && ch !== "\n" && (ch < " " || ch > "~")) { line.push(`&#${ch.charCodeAt(0)};`); } else { line.push(ch); } }; next(); while (i <= source.length) { ch = peek; next(); switch (ch) { case "<": line.push("<"); break; case ">": line.push(">"); break; case "&": line.push("&"); break; case "\"": line.push("""); break; case "'": line.push("'"); break; default: push(); } } endline(); return result.join("<br />"); }; const unique = (arr: any[], valFn: (item: any) => any) => { const values: { [key: string]: any } = {}; return arr.filter(item => { const val = String(valFn(item)); return val in values ? false : values[val] = true; }); }; const propertiesEqual = (a: any, b: any, properties: string[]) => a && b && properties.every((p: any) => a[p] === b[p]); const dateAdd = (date: Date, ms: number) => new Date(date.getTime() + ms); const getToday = () => { const d = new Date(); d.setHours(23, 59, 59); return d; }; export interface IPageInfo { from?: Date; to?: Date; page: number; } type Pager = (page: number) => IPageInfo; const historyPager = (msRange: number): Pager => { const today = getToday(); return (page: number) => { const from = dateAdd(today, -msRange * (page + 1)); from.setHours(0, 0, 0); let to: Date | undefined = dateAdd(today, -msRange * page); if (page < 1) { to = undefined; } return { from, to, page }; }; }; // only skips sunday const nextWorkDay = function (date: Date): Date { const tomorrow = new Date(date.setDate(date.getDate() + 1)); return tomorrow.getDay() % 7 ? tomorrow : nextWorkDay(tomorrow); }; // only skips sunday const lastWorkday = function (date: Date): Date { const yesterday = new Date(date.setDate(date.getDate() - 1)); return yesterday.getDay() % 7 ? yesterday : lastWorkday(yesterday); }; const isFileImage = (fileName: string) => { const extensions = [".JPG", ".JPEG", ".JPE", ".BMP", ".GIF", ".PNG"]; return extensions.some(x => fileName.toUpperCase().includes(x)); }; const isValidDate = (d: any) => d instanceof Date && !isNaN(d.getTime()); function getNumber(x: any) { const value = Number(String(x).replace(/([^\d])/, "").replace(/^0+/, "")); if (Number.isFinite(value)) return value; return undefined; } const isNumberKey = (event: any) => { const charCode = (event.which) ? event.which : event.keyCode; if (charCode !== 46 && charCode > 31 && (charCode < 48 || charCode > 57)) { return false; } return true; }; const escapeHtml = (unsafe: any): string => { return String(unsafe) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); }; const onChange = (objToWatch: any, fn: any) => { const handler = { set(target: any, property: any, value: any) { fn(); return Reflect.set(target, property, value); }, deleteProperty(target: any, property: any) { fn(); return Reflect.deleteProperty(target, property); } }; return new Proxy(objToWatch, handler); }; function debounced(delay: number, fn: (...args: Array<any>) => any) { let timerId: any; return function (...args: Array<any>) { if (timerId) { clearTimeout(timerId); } timerId = setTimeout(() => { fn(...args); timerId = null; }, delay); }; } const displayTextAreaProperly = (str: string): string => { if (str) { return str.replace(/\n/g, "<br>"); } return ""; }; const createCounter = () => { let c = 0; return () => { c += 1; return c; }; }; const getCircularReplacer = () => { const seen = new WeakSet(); return (_key: any, value: any) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; }; export function getDateString(date: Date) { const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}-${month}-${year}`; } export function jsonCopy<T>(x: T): T { return parseJson(toJson(x)) } const toJson = (x: any) => JSON.stringify(x, getCircularReplacer()); const parseJson = (x: any) => { if (typeof x !== "string") { return undefined; } try { return JSON.parse(x); } catch { return undefined; } }; export const getLocalStorage = <T>(key: string): T => parseJson(localStorage.getItem(key)) as T; export const setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state)); export const removeLocalStorage = (key: string): any => localStorage.removeItem(key); export const getSessionStorage = <T>(key: string): T => parseJson(sessionStorage.getItem(key)) as T; export const setSessionStorage = (key: string, state: any): any => sessionStorage.setItem(key, toJson(state)); const c = encodeURIComponent; const cv = (v: any) => v && v.toISOString ? v.toISOString() : v; const objToQueryString = (obj: any) => Object.keys(obj).map(k => `${c(k)}=${c(cv(obj[k]))}`).join("&"); function b64toBlob(b64Data: string, contentType: string, sliceSize?: number): Blob { contentType = contentType || ""; sliceSize = sliceSize || 512; const byteCharacters = atob(b64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { const slice = byteCharacters.slice(offset, offset + sliceSize); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } return new Blob(byteArrays, { type: contentType }); } function imagedataToBlob(imagedata: ImageData): Promise<Blob> { return new Promise((resolve, reject) => { try { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); if (!ctx) { reject(); return; } canvas.width = imagedata.width; canvas.height = imagedata.height; ctx.putImageData(imagedata, 0, 0); canvas.toBlob((blob) => { if (!blob) { reject(); return; } resolve(blob); }, "image/jpeg", 0.95); } catch (error) { reject(error); } }); } const arrRemove = (arr: Array<any>, item: any) => { const i = arr.indexOf(item); if (i >= 0) { arr.splice(i, 1); } }; const filterUnique = () => { const keys = new Set(); return (key: string) => !keys.has(key) ? keys.add(key) || true : false; }; const isSet = (x: any) => x != null; // works for [null, undefined], [ false, 0, Nan ] are true export const isNumber = (x: any) => typeof x === "number" && !Number.isNaN(x) && Number.isFinite(x); const isString = (x: any) => typeof x === "string"; const isIterable = (value: any) => Symbol.iterator in Object(value); function* iterator(iterable: Array<any>) { for (const item of iterable) { yield item; } } function* zipIterables(...iterables: Array<any>) { const iterators = iterables.map(x => iterator(x)); while (true) { const stats = []; const results = []; for (const iterable of iterators) { const result = iterable.next(); stats.push(result.done); results.push(result.value); } if (stats.every((stat) => stat === true)) { break; } yield results; } } const stringComparer = (locales?: any, options?: any) => { if (!locales) { locales = "en"; } if (!options) { options = { numeric: true, sensitivity: "base" }; } const collator = new Intl.Collator(locales, options); return (a: string, b: string) => collator.compare(a, b); }; const comparer = stringComparer(); function compare(a: any, b: any, reverse: boolean): number { const [x, y] = reverse ? [b, a] : [a, b]; if (!isSet(x) && !isSet(y)) { return 0; } if (!isSet(x) && isSet(y)) { return -1; } if (!isSet(y) && isSet(x)) { return 1; } if (isNumber(x) && isNumber(y)) { return x - y; } if (isString(x) && isString(y)) { return comparer(x, y); } if (isIterable(x) && isIterable(y)) { for (const d of Array.from(zipIterables(x, y))) { const c = compare(d[0], d[1], reverse); if (c !== 0) { return c; } } return 0; } return comparer(String(x), String(y)); } export function orderBy<T>(array: T[], fn: (x: T) => any, reverse?: boolean) { const result = Array.from(array); result.sort((a, b) => compare(fn(a), fn(b), reverse || false)); return result; } export function getDate(input: any): Date | undefined { if (typeof input === "object" && input instanceof Date) { return new Date(input); } if (typeof input === "string") { return new Date(input); } return undefined; } export function numberFormat(value?: number, unit: string = "", showZero = false) { if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; if (!showZero && value === 0) return ""; const currencyFormatter = new Intl.NumberFormat("nl-NL"); return currencyFormatter.format(value) + (unit ? ` ${unit}` : ""); } export function numberFormatRounded(value?: number, decimals: number = 0, unit: string = "", showZero = false) { if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; if (!showZero && value === 0) return ""; const roundedValue = round(value || 0, decimals) ?? 0; const isInteger = roundedValue % 1 === 0; const currencyFormatter = new Intl.NumberFormat("nl-NL", { minimumFractionDigits: isInteger ? 0 : decimals, maximumFractionDigits: decimals }); return currencyFormatter.format(roundedValue) + (unit ? ` ${unit}` : ""); } export function currencyFormat(value: number | undefined, currencyISO: string = "EUR"): string { if (value === null || value === undefined) return ""; if (typeof (value) !== "number" || Number.isNaN(value) || !Number.isFinite(value)) return ""; const currencyFormatter = new Intl.NumberFormat("nl-NL", { style: "currency", currency: currencyISO, minimumFractionDigits: 2, maximumFractionDigits: 6 }); return currencyFormatter.format(value); } export function round(value: number | undefined, precision: number): number | undefined { if (value === null || value === undefined) return undefined; value = Number(value) || 0.0; const multiplier = Math.pow(10, precision || 0); return Math.round(value * multiplier) / multiplier; } export function getNumberFromString(value: any): number | undefined { if (value === null || value === undefined || value === "") return undefined; const s = String(value).replace(/[^\d.,]/, ""); const parts = s.split(/[.,]/); if (parts.length > 1) parts.splice(parts.length - 1, 0, "."); // insert at index const j = parts.join(""); return parseFloat(j) || 0.0; } export const doubleIsEqualTo = (a: number, b: number, maxDiff: number = Number.EPSILON) => Math.abs(b - a) <= maxDiff; interface IGroup<U> { group: string; data: U; } export function groupBy<T, U>(array: T[], fn: (item: T) => IGroup<U>) { const groups: { [key: string]: U[] } = {}; array.forEach((item) => { const result = fn(item); const group = JSON.stringify(result.group); groups[group] = groups[group] || []; groups[group].push(result.data); }); return Object.keys(groups).map((group) => groups[group]); } export const getDateXDaysAgo = (numOfDays: number, date = new Date()) => { const daysAgo = new Date(date.getTime()); daysAgo.setDate(date.getDate() - numOfDays); return daysAgo; } const nameof = <T>(name: Extract<keyof T, string>): string => name; export function propertyOf<T>(name: keyof T) { return name; } export function pushRange(arr: any[], itemsToPush: any[]) { Array.prototype.push.apply(arr, itemsToPush); } export interface IHaveParentId { id: any; parentId?: any; } interface IHasChilds { children: any[]; } export function createDataTree<T extends IHasChilds>(dataset: IHaveParentId[]) { const hashTable = Object.create(null); dataset.forEach(aData => hashTable[aData.id] = { ...aData }); const dataTree: T[] = []; dataset.forEach(aData => { if (aData.parentId) { const parent = hashTable[aData.parentId]; parent.children ??= []; parent.children.push(hashTable[aData.id]); } else dataTree.push(hashTable[aData.id]) }); return dataTree; } const toBase64 = (file: Blob) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); function* rangeGen(from: number, to: number) { let i = from; while (i <= to) { yield i; i++; } } export function range(from: number, to: number) { return Array.from(rangeGen(from, to)); } export function sumArray<T>(arr: T[], fn: (arg: T) => number): number { return arr.reduce((p, c) => p + fn(c), 0); } export function isHttpSucces(status?: number) { return !!status && status >= 200 && status < 400; } export function bytesToKBMB(bytes?: number) { if (!bytes) return; if (bytes < 1024) { return bytes + " bytes"; } else if (bytes < 1024 * 1024) { return (bytes / 1024).toFixed(2) + " KB"; } else { return (bytes / (1024 * 1024)).toFixed(2) + " MB"; } } export function IsNullOrEmpty(text: string) { if (text === undefined || text === null || text === "") return true; return false; } export async function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } export interface IPathPart { name: string; path: string; isRootPath: boolean; } export function splitStringOnBackslashWithPath(str: string, rootPath: string): IPathPart[] { const splitted = str.split("\\"); const response: IPathPart[] = []; const prevPath: string[] = []; for (const item of splitted) { prevPath.push(item); const path = prevPath.join("\\"); response.push({ name: item, path: path, isRootPath: rootPath.includes(path) }) } return response; } export function getDateFromNumber(numberDate: number): string { const dateStr = numberDate.toString(); const year = parseInt(dateStr.substring(0, 4), 10); const month = parseInt(dateStr.substring(4, 6), 10); const day = parseInt(dateStr.substring(6, 8), 10); const date = new Date(year, month - 1, day); // month is 0-indexed in Date const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', year: 'numeric' }; return new Intl.DateTimeFormat('en-GB', options).format(date); } export const classes = (...args: any[]) => Array.from(args).map(x => String(x).trim()).filter(x => !!x).join(" "); export { arrRemove, b64toBlob, createCounter, dateAdd, debounced, displayTextAreaProperly, eq, escapeHtml, filterUnique, getFirstItem, getNumber, getString, getToday, historyPager, htmlEncode, imagedataToBlob, isFileImage, isNumberKey, isset, isValidDate, lastWorkday, nameof, nextWorkDay, objToQueryString, onChange, propertiesEqual, remove, strEq, toBase64, unique };
939200cookie-checkTypescript helpers (tshelpers)