///
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
},
}
}