Spaces:
Running
on
L40S
Running
on
L40S
/** | |
* @fileoverview | |
* A bunch of shared utils that can be used in ComfyUI, as well as in any single-HTML pages. | |
*/ | |
export type Resolver<T> = { | |
id: string; | |
completed: boolean; | |
resolved: boolean; | |
rejected: boolean; | |
promise: Promise<T>; | |
resolve: (data: T) => void; | |
reject: (e?: Error) => void; | |
timeout: number | null; | |
deferment?: {data?: any, timeout?: number|null, signal?: string}; | |
}; | |
/** | |
* Returns a new `Resolver` type that allows creating a "disconnected" `Promise` that can be | |
* returned and resolved separately. | |
*/ | |
export function getResolver<T>(timeout: number = 5000): Resolver<T> { | |
const resolver: Partial<Resolver<T>> = {}; | |
resolver.id = generateId(8); | |
resolver.completed = false; | |
resolver.resolved = false; | |
resolver.rejected = false; | |
resolver.promise = new Promise((resolve, reject) => { | |
resolver.reject = (e?: Error) => { | |
resolver.completed = true; | |
resolver.rejected = true; | |
reject(e); | |
}; | |
resolver.resolve = (data: T) => { | |
resolver.completed = true; | |
resolver.resolved = true; | |
resolve(data); | |
}; | |
}); | |
resolver.timeout = setTimeout(() => { | |
if (!resolver.completed) { | |
resolver.reject!(); | |
} | |
}, timeout); | |
return resolver as Resolver<T>; | |
} | |
/** The WeakMap for debounced functions. */ | |
const DEBOUNCE_FN_TO_PROMISE: WeakMap<Function, Promise<void>> = new WeakMap(); | |
/** | |
* Debounces a function call so it is only called once in the initially provided ms even if asked | |
* to be called multiple times within that period. | |
*/ | |
export function debounce(fn: Function, ms = 64) { | |
if (!DEBOUNCE_FN_TO_PROMISE.get(fn)) { | |
DEBOUNCE_FN_TO_PROMISE.set( | |
fn, | |
wait(ms).then(() => { | |
DEBOUNCE_FN_TO_PROMISE.delete(fn); | |
fn(); | |
}), | |
); | |
} | |
return DEBOUNCE_FN_TO_PROMISE.get(fn); | |
} | |
/** Waits a certain number of ms, as a `Promise.` */ | |
export function wait(ms = 16): Promise<void> { | |
// Special logic, if we're waiting 16ms, then trigger on next frame. | |
if (ms === 16) { | |
return new Promise((resolve) => { | |
requestAnimationFrame(() => { | |
resolve(); | |
}); | |
}); | |
} | |
return new Promise((resolve) => { | |
setTimeout(() => { | |
resolve(); | |
}, ms); | |
}); | |
} | |
function dec2hex(dec: number) { | |
return dec.toString(16).padStart(2, "0"); | |
} | |
/** Generates an unique id of a specific length. */ | |
export function generateId(length: number) { | |
const arr = new Uint8Array(length / 2); | |
crypto.getRandomValues(arr); | |
return Array.from(arr, dec2hex).join(""); | |
} | |
/** | |
* Returns the deep value of an object given a dot-delimited key. | |
*/ | |
export function getObjectValue(obj: any, objKey: string, def?: any) { | |
if (!obj || !objKey) return def; | |
const keys = objKey.split("."); | |
const key = keys.shift()!; | |
const found = obj[key]; | |
if (keys.length) { | |
return getObjectValue(found, keys.join("."), def); | |
} | |
return found; | |
} | |
/** | |
* Sets the deep value of an object given a dot-delimited key. | |
* | |
* By default, missing objects will be created while settng the path. If `createMissingObjects` is | |
* set to false, then the setting will be abandoned if the key path is missing an intermediate | |
* value. For example: | |
* | |
* setObjectValue({a: {z: false}}, 'a.b.c', true); // {a: {z: false, b: {c: true } } } | |
* setObjectValue({a: {z: false}}, 'a.b.c', true, false); // {a: {z: false}} | |
* | |
*/ | |
export function setObjectValue(obj: any, objKey: string, value: any, createMissingObjects = true) { | |
if (!obj || !objKey) return obj; | |
const keys = objKey.split("."); | |
const key = keys.shift()!; | |
if (obj[key] === undefined) { | |
if (!createMissingObjects) { | |
return; | |
} | |
obj[key] = {}; | |
} | |
if (!keys.length) { | |
obj[key] = value; | |
} else { | |
if (typeof obj[key] != "object") { | |
obj[key] = {}; | |
} | |
setObjectValue(obj[key], keys.join("."), value, createMissingObjects); | |
} | |
return obj; | |
} | |
/** | |
* Moves an item in an array (by item or its index) to another index. | |
*/ | |
export function moveArrayItem<T>(arr: T[], itemOrFrom: T | number, to: number) { | |
const from = typeof itemOrFrom === "number" ? itemOrFrom : arr.indexOf(itemOrFrom); | |
arr.splice(to, 0, arr.splice(from, 1)[0]!); | |
} | |
/** | |
* Moves an item in an array (by item or its index) to another index. | |
*/ | |
export function removeArrayItem<T>(arr: T[], itemOrIndex: T | number) { | |
const index = typeof itemOrIndex === "number" ? itemOrIndex : arr.indexOf(itemOrIndex); | |
arr.splice(index, 1); | |
} | |
/** | |
* Injects CSS into the page with a promise when complete. | |
*/ | |
export function injectCss(href: string): Promise<void> { | |
if (document.querySelector(`link[href^="${href}"]`)) { | |
return Promise.resolve(); | |
} | |
return new Promise((resolve) => { | |
const link = document.createElement("link"); | |
link.setAttribute("rel", "stylesheet"); | |
link.setAttribute("type", "text/css"); | |
const timeout = setTimeout(resolve, 1000); | |
link.addEventListener("load", (e) => { | |
clearInterval(timeout); | |
resolve(); | |
}); | |
link.href = href; | |
document.head.appendChild(link); | |
}); | |
} | |
/** | |
* Calls `Object.defineProperty` with special care around getters and setters to call out to a | |
* parent getter or setter (like a super.set call) to ensure any side effects up the chain | |
* are still invoked. | |
*/ | |
export function defineProperty(instance: any, property: string, desc: PropertyDescriptor) { | |
const existingDesc = Object.getOwnPropertyDescriptor(instance, property); | |
if (existingDesc?.configurable === false) { | |
throw new Error(`Error: rgthree-comfy cannot define un-configurable property "${property}"`); | |
} | |
if (existingDesc?.get && desc.get) { | |
const descGet = desc.get; | |
desc.get = () => { | |
existingDesc.get!.apply(instance, []); | |
return descGet!.apply(instance, []); | |
}; | |
} | |
if (existingDesc?.set && desc.set) { | |
const descSet = desc.set; | |
desc.set = (v: any) => { | |
existingDesc.set!.apply(instance, [v]); | |
return descSet!.apply(instance, [v]); | |
}; | |
} | |
desc.enumerable = desc.enumerable ?? existingDesc?.enumerable ?? true; | |
desc.configurable = desc.configurable ?? existingDesc?.configurable ?? true; | |
if (!desc.get && !desc.set) { | |
desc.writable = desc.writable ?? existingDesc?.writable ?? true; | |
} | |
return Object.defineProperty(instance, property, desc); | |
} | |