import _ from 'lodash'; import {AnyFunction} from './types'; export type JSONSerializable = { [key: string]: any } export type JSONValue = string | number | boolean | undefined | JSONObject; export interface JSONObject { [key: string]: JSONValue; } export function randomText(length: number) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } export const uuidV4 = function () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; export class IncrementId { private _id: string; private readonly _prefix: string; constructor(prefix?: string) { this._prefix = prefix ? prefix : ''; this._id = this._prefix + '0'; } getId() { const {_id, _prefix} = this; if (!_id) { this._id = _prefix + '0'; } else { const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length); const newIdNum = parseInt(idNumStr, 10) + 1; this._id = _prefix + newIdNum.toString(); } return this._id; } } export function incrementId(prefix?: string) { const _prefix = prefix ? prefix : ''; let _id = _prefix + '0'; return function id() { const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length); const newIdNum = parseInt(idNumStr, 10) + 1; _id = _prefix + newIdNum.toString(); return _id; }; } export const getValue = (obj: T, names: K[]): Array => { return names.map(i => obj[i]); }; export const isObject = (object: string | JSONObject | boolean | AnyFunction | number) => { return object != null && typeof object === 'object'; }; export const looseEqual = (a: any, b: any): boolean => { return a == b; }; export const strictEqual = (a: any, b: any): boolean => { return a === b; }; export const strictObjectIsEqual = (a: any, b: any): boolean => { return Object.is(a, b); }; export const deepObjectStrictEqual = (object1: JSONSerializable, object2: JSONSerializable) => { const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { return false; } for (const key of keys1) { const val1 = object1[key]; const val2 = object2[key]; const areObjects = isObject(val1) && isObject(val2); if ( areObjects && !deepObjectStrictEqual(val1, val2) || !areObjects && val1 !== val2 ) { return false; } } return true; }; export const isTypeEqual = (obj: unknown) => { const m = obj as unknown as T; }; export function reverseColor(oldColor: string) { const oldColorTemp = '0x' + oldColor.replace(/#/g, ''); const str = '000000' + (0xFFFFFF - Number(oldColorTemp)).toString(16); return '#' + str.substring(str.length - 6, str.length); } export const isSameStructure = (objA: unknown, objB: unknown) => { const objATraversable = objA as JSONSerializable; const objBTraversable = objB as JSONSerializable; const objAKeys = Object.keys(objATraversable); const objBKeys = Object.keys(objBTraversable); let isSame = true; if (objAKeys.length !== objBKeys.length) { return isSame = false; } else { objAKeys.forEach((i) => { if (!objBKeys.includes(i)) { return isSame = false; } }); return isSame; } }; export const isLeafParent = (obj: JSONObject) => { let isLeaf = true; Object.values(obj).forEach(value => { if (typeof value === 'object' && value instanceof Array) { value.forEach(item => { if (typeof item === 'object') { return false; } }); return isLeaf = true; } if (!['string', 'boolean', 'number', 'undefined', 'function'].includes(typeof value) && (value !== null)) { return isLeaf = false; } }); return isLeaf; }; export const addDays = (date: Date, days: number): Date => { date.setDate(date.getDate() + days); return date; }; export class WaitManager { private _time1 = 1000; get time1(): number { return this._time1 / this._nXSpeed; } private _time2 = 2000; get time2(): number { return this._time2 / this._nXSpeed; } private _time3 = 3000; get time3(): number { return this._time3 / this._nXSpeed; } private _time4 = 4000; get time4(): number { return this._time4 / this._nXSpeed; } private _time10 = 10000; get time10(): number { return this._time10 / this._nXSpeed; } private _time20 = 20000; get time20(): number { return this._time20 / this._nXSpeed; } private _time30 = 20000; get time50(): number { return this._time30 / this._nXSpeed; } private _time60 = 60000; get time60(): number { return this._time60 / this._nXSpeed; } private _cusTime = 1000; get cusTime(): number { return this._cusTime / this._nXSpeed; } set cusTime(v: number) { this._cusTime = v; } private readonly _nXSpeed: number = 1; constructor(nXSpeed?: number) { if (nXSpeed === undefined) nXSpeed = 1; this._nXSpeed = nXSpeed; } } export const wait = async (ms: number, resolveValue?: any) => { return new Promise((resolve, reject) => { setTimeout(() => { const finalResolveValue = resolveValue || true; resolve(finalResolveValue); }, ms); }); }; export class AuthAPIError extends Error { protected serverErrorStack; protected serverErrorCode; constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { super(serverErrorMessage); if (serverErrorStack) { this.serverErrorStack = serverErrorStack; } if (serverErrorCode) { this.serverErrorCode = serverErrorCode; } this.name = new.target.name; if (typeof (Error as any).captureStackTrace === 'function') { (Error as any).captureStackTrace(this, new.target); } if (typeof Object.setPrototypeOf === 'function') { Object.setPrototypeOf(this, new.target.prototype); } else { (this as any).__proto__ = new.target.prototype; } } } export class BunnyAPIError extends Error { protected serverErrorStack; protected serverErrorCode; constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { super(serverErrorMessage); if (serverErrorStack) { this.serverErrorStack = serverErrorStack; } if (serverErrorCode) { this.serverErrorCode = serverErrorCode; } this.name = new.target.name; if (typeof (Error as any).captureStackTrace === 'function') { (Error as any).captureStackTrace(this, new.target); } if (typeof Object.setPrototypeOf === 'function') { Object.setPrototypeOf(this, new.target.prototype); } else { (this as any).__proto__ = new.target.prototype; } } } export class NomicsAPIError extends Error { protected serverErrorStack; protected serverErrorCode; constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { super(serverErrorMessage); if (serverErrorStack) { this.serverErrorStack = serverErrorStack; } if (serverErrorCode) { this.serverErrorCode = serverErrorCode; } this.name = new.target.name; if (typeof (Error as any).captureStackTrace === 'function') { (Error as any).captureStackTrace(this, new.target); } if (typeof Object.setPrototypeOf === 'function') { Object.setPrototypeOf(this, new.target.prototype); } else { (this as any).__proto__ = new.target.prototype; } } } export function extractValue(data: { key: string, value: Item }[]) { let result: Item[] = []; if (data && data.length > 0) { result = data.map(item => item.value); } return result; } export function keyValueToArray(data: { [key: string]: Item }) { const itemArray: Array = []; const keys = Object.keys(data); for (const i of keys) { itemArray.push({...data[i], _id: i}); } return itemArray; } export function minuted(time: number) { const minutes = Math.floor(time / 60000).toString(); const seconds = Math.floor((time % 60000) / 1000).toString().padStart(2, '0'); return `${minutes}:${seconds}`; } export function randomDate(start?: Date, end?: Date, specificProbabilityStart?: Date, specificProbability?: number) { if (!start) start = new Date('1970-1-1'); if (!end) end = new Date(); if (specificProbabilityStart) { if (!specificProbability) specificProbability = 0.5; if (Math.random() <= specificProbability) { return new Date(specificProbabilityStart.getTime() + Math.random() * (end.getTime() - specificProbabilityStart.getTime())); } } return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); } export const capitalizeWords = (str: string) => { return str.replace(/(?:^|\s)\S/g, (a: string) => a.toUpperCase()); }; export const capitalizeFirstLetter = (str: string) => { return str.charAt(0).toUpperCase() + str.slice(1); }; export const comparerArray = (otherArray: T[], limitKeys?: string[]) => { return function (current: T) { return otherArray.filter(function (other: T) { if (!limitKeys) { return _.isEqual(current, other); } else { // TODO } }).length == 0; }; }; export const onlyInA = (a: T[], b: T[]) => { return a.filter(comparerArray(b)); }; export const onlyInB = (a: T[], b: T[]) => { return b.filter(comparerArray(a)); }; export const diffAB = (a: T[], b: T[]) => { return onlyInA(a, b).concat(onlyInB(a, b)); }; export class StringUtil { // camelCase static toCamelCase(str: string) { return _.camelCase(str); } // snake_case static toSnakeCase(str: string) { return _.snakeCase(str); } // PascalCase static toPascalCase(str: string) { return _.startCase(_.camelCase(str)).replace(/ /g, ''); } // CONSTANT_CASE static toConstantCase(str: string) { return _.upperCase(str).replace(/ /g, '_'); } // kebab-case static toKebabCase(str: string) { return _.kebabCase(str); } // lowercase static toLowerCase(str: string) { return _.lowerCase(str).replace(/ /g, ''); } // Title Case static toTitleCase(str: string) { return _.startCase(_.camelCase(str)); } // Sentence case static toSentenceCase(str: string) { return _.upperFirst(_.lowerCase(str)); } // path/case static toPathCase(str: string) { return _.lowerCase(str).replace(/ /g, '/'); } // dot.case static toDotCase(str: string) { return _.lowerCase(str).replace(/ /g, '.'); } } type ToCase = 'camel' | 'snake' | 'pascal' | 'constant' | 'kebab' | 'lower' | 'title' | 'sentence' | 'path' | 'dot'; export const deepKeysConvert = (obj: any, toType?: ToCase): any => { const _toType = toType || 'snake'; if (Array.isArray(obj)) { return obj.map(v => deepKeysConvert(v, _toType)); } else if (obj !== null && obj.constructor === Object) { return Object.keys(obj).reduce( (result, key) => { let newKey = ''; switch (_toType) { case 'camel': newKey = StringUtil.toCamelCase(key); break; case 'snake': newKey = StringUtil.toSnakeCase(key); break; case 'pascal': newKey = StringUtil.toPascalCase(key); break; case 'constant': newKey = StringUtil.toConstantCase(key); break; case 'kebab': newKey = StringUtil.toKebabCase(key); break; case 'lower': newKey = StringUtil.toLowerCase(key); break; case 'title': newKey = StringUtil.toTitleCase(key); break; case 'sentence': newKey = StringUtil.toSentenceCase(key); break; case 'path': newKey = StringUtil.toPathCase(key); break; case 'dot': newKey = StringUtil.toDotCase(key); break; default: newKey = StringUtil.toDotCase(key); break; } return { ...result, [newKey]: deepKeysConvert(obj[key], _toType), }; }, {}, ); } return obj; }; export const deepRemoveByKey = (obj: any, keysToBeRemoved: string[]) => { const result = _.transform(obj, function (result: JSONSerializable, value: any, key: string) { if (_.isObject(value)) { value = deepRemoveByKey(value, keysToBeRemoved); } if (!keysToBeRemoved.includes(key)) { _.isArray(obj) ? result.push(value) : result[key] = value; } }); return result as typeof obj; }; export const deepRenameKeys = (obj: JSONSerializable, keysMap: { [key in string]: string }) => { return _.transform(obj, function (result: JSONSerializable, value: any, key: string | number) { const currentKey = keysMap[key] || key; result[currentKey] = _.isObject(value) ? deepRenameKeys(value, keysMap) : value; }); }; export const deepReplaceValues = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }) => { const newObject = _.clone(obj) as JSONSerializable; _.each(obj, (val: any, key: string) => { for (const item in keyReducerMap) { if (key === item) { newObject[key] = keyReducerMap[item](newObject); } else if (typeof (val) === 'object' || val instanceof Array) { newObject[key] = deepReplaceValues(val, keyReducerMap); } } }); return newObject; }; // function getCallStackSize() { // let count = 0, fn = arguments.callee; // while ( (fn = fn.caller) ) { // count++; // } // return count; // } // TODO determine depth and pass root node as a param through callback export const deepAdd = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }, isItemRootParent?: boolean) => { const newObject = _.clone(obj) as JSONObject | []; if (_.isObject(newObject) && !_.isArray(newObject)) { for (const item in keyReducerMap) { newObject[item] = keyReducerMap[item](newObject); } } _.each(obj, (val: any, key: string | number) => { if (_.isObject(val)) { for (const item in keyReducerMap) { // @ts-ignore newObject[key] = deepAdd(val, keyReducerMap, isItemRootParent); } } }); return newObject; }; const styleString = (color: string) => `color: ${color}; font-weight: bold`; const styleHeader = (header: string) => `%c[${header}]`; export const bunnyConsole = { log: (headerLog = 'bunny', ...args: any[]) => { return console.log(styleHeader(headerLog), styleString('black'), ...args); }, warn: (headerLog = 'bunny', ...args: any[]) => { return console.warn(styleHeader(headerLog), styleString('orange'), ...args); }, error: (headerLog = 'bunny', ...args: any[]) => { return console.error(styleHeader(headerLog), styleString('red'), ...args); } }; export const timeStart = () => { return performance ? performance.now() : new Date().getTime(); }; export const timeEnd = (startTime: number, headerLog?: string, consoleConditionFn?: (timeSpent: number) => boolean) => { const timeSpent = (performance ? performance.now() : new Date().getTime()) - startTime; const isPassCondition = consoleConditionFn ? consoleConditionFn(timeSpent) : true; if (isPassCondition) { bunnyConsole.log(headerLog ? headerLog : 'time spent', timeSpent.toFixed(2)); } }; export const arrayRemove = function (array: T[], predicate: (item: T, index: number, array: T[]) => boolean): T[] { let i = -1, len = array ? array.length : 0; const result = []; while (++i < len) { const value = array[i]; if (predicate(value, i, array)) { result.push(value); Array.prototype.splice.call(array, i--, 1); len--; } } return result; }; export function memo() { const cache: { [k: string]: any } = {}; // eslint-disable-next-line @typescript-eslint/ban-types return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { const cacheKey = `__cacheKey__${args.toString()}`; // eslint-disable-next-line no-prototype-builtins if (!cache.hasOwnProperty(cacheKey)) { cache[cacheKey] = originalMethod.apply(this, args); } return cache[cacheKey]; } } } export function zip(array1: T[], array2: T1[], options?: { isToObj: boolean }) { const zipped: [T, T1][] = []; const zippedObjCoords: { x: T, y: T1 }[] = []; const {isToObj} = options ? options : {isToObj: false}; for (let i = 0; i < array1.length; i++) { if (isToObj) { zippedObjCoords.push({x: array1[i], y: array2[i]}) } else { zipped.push([array1[i], array2[i]]); } } return isToObj ? zippedObjCoords : zipped; }