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)