/// namespace Leaf { export class Util { constructor() { } static clone(obj: T): T { if (!obj) return obj if (Array.isArray(obj)) { return obj.map((item) => { return Util.clone(item) }) as any as T } if (obj instanceof Date) { return obj as any as T } if (obj instanceof RegExp) { let i = obj.ignoreCase && "i" || "" let g = obj.global && "g" || "" let m = obj.multiline && "m" || "" return new RegExp(obj.source, i + g + m) as any as T } if (obj instanceof String) { return new String(obj) as any as T } if (typeof obj === "function") { return obj } if (typeof obj === "object") { let result: any = {} for (let prop in obj) { result[prop] = this.clone(obj[prop] as any) } return result as any as T } // for RegExp/Function we left it as is. return obj } static capitalize(str: string): string { if (!str) return str return str.charAt(0).toUpperCase() + str.slice(1) } static uncapitalize(str: string): string { if (!str) return str return str.charAt(0).toLowerCase() + str.slice(1) } static camelToSlug(str: string): string { if (!str) return str return str.replace(/[a-z][A-Z]/g, (match) => { return match[0] + "-" + match[1].toLowerCase() }).toLowerCase() } static slugToCamel(str: string): string { if (!str) return str return str.replace(/-[a-z]/ig, (match) => { return match.slice(1).toUpperCase() }) } static deepEqual(a: any, b: any): boolean { if (a === b) return true if (typeof a !== typeof b) return false if (typeof a !== "object") return false if (Array.isArray(a)) { if (a.length != b.length) return false for (let i = 0; i < a.length; i++) { if (!this.deepEqual(a[i], b[i])) return false } return true } else { if (!a || !b) { return false } let pas = Object.getOwnPropertyNames(a) let pbs = Object.getOwnPropertyNames(b) if (pas.length != pbs.length) { return false } let diff = this.arrayDiff(pas, pbs) if (diff.left.length !== 0 || diff.right.length !== 0) return false for (let key of diff.intersect) { if (!this.deepEqual(a[key], b[key])) { return false } } return true } } static dictDiff(d1: { [index: string]: T }, d2: { [index: string]: T }, identical?: (a: T, b: T) => boolean): { left: string[] right: string[] interact: string[] intersect?: string[] } { let left = Object.keys(d1).map((name) => { return { name: name, value: d1[name] } }) let right = Object.keys(d2).map((name) => { return { name: name, value: d2[name] } }) if (!identical) { identical = (a, b) => { return a === b } } let result = this.arrayDiff(left, right, (a, b) => { return identical(a.value, b.value) }) return { left: result.left.map(item => item.name), right: result.right.map(item => item.name), interact: result.interact.map(item => item.name), intersect: result.intersect.map(item => item.name) } } static arrayDiffTyped(arr1: TLeft[], arr2: TRight[], identical?: (a: TLeft, b: TRight) => boolean): { left: TLeft[] right: TRight[] leftShare: TLeft[] rightShare: TRight[] } { if (!identical) { identical = (a: any, b: any) => { return a === b } } arr1 = arr1.slice() arr2 = arr2.slice() let leftShare: TLeft[] = [] let rightShare: TRight[] = [] for (let i = 0; i < arr1.length; i++) { if (!arr1[i]) break for (let j = 0; j < arr2.length; j++) { if (identical(arr1[i], arr2[j])) { leftShare.push(arr1[i]) rightShare.push(arr2[j]) arr1.splice(i, 1) arr2.splice(j, 1) i--; break; } } } return { left: arr1, right: arr2, leftShare, rightShare } } static arrayDiff(arr1: T[] = [], arr2: T[] = [], identical?: (a: T, b: T) => boolean): { left: T[] right: T[] interact: T[] intersect: T[] } { if (!identical) { identical = (a, b) => { return a === b } } arr1 = arr1.slice() arr2 = arr2.slice() let intersect: T[] = [] for (let i = 0; i < arr1.length; i++) { if (!arr1[i]) break for (let j = 0; j < arr2.length; j++) { if (identical(arr2[j], arr1[i])) { intersect.push(arr1[i]) arr1.splice(i, 1) arr2.splice(j, 1) i--; break; } } } return { left: arr1, right: arr2, interact: intersect, intersect, } } static buildOption(defaultOption: T, option: Partial = {}): T { if (defaultOption == null || typeof defaultOption == "undefined") { return option as T } if (typeof defaultOption !== "undefined" && defaultOption !== null && (typeof option === "undefined" || option === null)) { return defaultOption } let result: T = {} as T if (Array.isArray(option)) { return option as any } if (Array.isArray(defaultOption)) { if (!option) { return defaultOption.slice() as any } else { return option as any } } if (typeof option === "object") { for (let name in defaultOption) { if (typeof option[name] !== "undefined") { result[name] = this.buildOption(defaultOption[name], option[name] as any) } else { result[name] = defaultOption[name] } } return result } return option } static getBrowserInfo(): BrowserInfo { let unknown = { name: "None", version: "None", mobile: false } if (typeof navigator === "undefined") { return unknown } let N = navigator.appName let ua = navigator.userAgent if (!ua) return unknown let M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i); let tem = ua.match(/version\/([\.\d]+)/i) if (M && tem != null) { M[2] = tem[1] } M = M ? [M[1], M[2]] : [N, navigator.appVersion, '-?'] return { name: M[0], version: M[1], mobile: Util.isMobile() } } static isMobile(): boolean { if (navigator && navigator.userAgent) { return (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i)) && true || false } else { return false } } static browser: BrowserInfo static isEmptyValue(v) { if (typeof v === "undefined" || v === null) { return true } return false } static getObjectValue(obj: any, path: string) { let parent = obj let parts = path.split(".") while (!Util.isEmptyValue(parent) && parts.length > 0) { let part = parts.shift() parent = parent[part] } return parent } static reconstructObjectMap(map: any) { let result = {} Object.keys(map).forEach(key => { let value = map[key] as string let routes = key.split(".") let node = result let ck: string = null while (ck = routes.shift()) { if (routes.length == 0) { node[ck] = value break } if (typeof node[ck] == "undefined" || node[ck] === null) { node[ck] = {} } node = node[ck] } }) return result } } export interface BrowserInfo { name: string version: string mobile: boolean } Property.define(Util, "browser", { get: (cache) => { if (!cache.value) { cache.value = Util.getBrowserInfo() } return cache.value } }) export interface SharedCallbackFunction { (...args: any[]): void push?: { (fn: Function): void } clear?: { (): void } callbacks?: Function[] count?: number } export const SharedCallbacks = { create(): SharedCallbackFunction { let fn: SharedCallbackFunction = function (...args: any[]): void { let cbs: Function[] = fn.callbacks.slice(0) fn.clear() cbs.forEach(function (callback) { callback(...args) }) } fn.callbacks = [] fn.push = function (callback: Function) { fn.callbacks.push(callback) fn.count = fn.callbacks.length } fn.clear = function () { fn.callbacks.length = 0 fn.count = 0 } return fn }, } }