UNPKG

@etsoo/shared

Version:

TypeScript shared utilities and functions

198 lines (197 loc) 6.58 kB
const hasRequestAnimationFrame = typeof requestAnimationFrame === "function"; /** * Extend utilities */ export var ExtendUtils; (function (ExtendUtils) { /** * Apply mixins, official suggested method * https://www.typescriptlang.org/docs/handbook/mixins.html#understanding-the-sample * @param derivedCtor Mixin target class * @param baseCtors Mixin base classes * @param override Override or not */ function applyMixins(derivedCtor, baseCtors, override = false) { baseCtors.forEach((baseCtor) => { Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { if (name !== "constructor" && (override || !derivedCtor.prototype[name])) { Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null)); } }); }); } ExtendUtils.applyMixins = applyMixins; /** * Create delayed executor * @param func Function * @param delayMiliseconds Delay miliseconds * @returns Result */ function delayedExecutor(func, delayMiliseconds) { let cancel; return { /** * Call the function * @param miliseconds Delayed miliseconds for this call * @param args Args */ call(miliseconds, ...args) { this.clear(); cancel = waitFor(() => { func(...args); cancel = undefined; }, miliseconds ?? delayMiliseconds); }, /** * Clear */ clear() { if (this.isRunning()) { if (cancel) cancel(); cancel = undefined; } }, /** * Is running or not * @returns Result */ isRunning() { return cancel != null; } }; } ExtendUtils.delayedExecutor = delayedExecutor; /** * Promise handler to catch error * @param promise Promise */ ExtendUtils.promiseHandler = (promise) => promise .then((value) => [value, undefined]) .catch((reason) => Promise.resolve([undefined, reason])); /** * Delay promise * @param delay Delay miniseconds */ function sleep(delay = 0) { return new Promise((resolve) => { waitFor(resolve, delay); }); } ExtendUtils.sleep = sleep; /** * Wait for condition meets and execute callback * requestAnimationFrame to replace setTimeout * @param callback Callback * @param checkReady Check ready, when it's a number as miliseconds, similar to setTimeout * @returns cancel callback */ function waitFor(callback, checkReady) { let requestID; if (hasRequestAnimationFrame) { let lastTime; function loop(timestamp) { // Cancelled if (requestID == null) return; if (lastTime === undefined) { lastTime = timestamp; } const elapsed = timestamp - lastTime; const isReady = typeof checkReady === "number" ? elapsed >= checkReady : checkReady(elapsed); if (isReady) { callback(); } else if (requestID != null) { // May also be cancelled in callback or somewhere requestID = requestAnimationFrame(loop); } } requestID = requestAnimationFrame(loop); } else if (typeof checkReady === "number") { requestID = setTimeout(callback, checkReady); } else { // Bad practice to use setTimeout in this way, only for compatibility const ms = 20; let spanTime = 0; let cr = checkReady; function loop() { // Cancelled if (requestID == null) return; spanTime += ms; if (cr(spanTime)) { callback(); } else if (requestID != null) { // May also be cancelled in callback or somewhere requestID = setTimeout(loop, ms); } } requestID = setTimeout(loop, ms); } return () => { if (requestID) { if (hasRequestAnimationFrame && typeof requestID === "number") { cancelAnimationFrame(requestID); } else { clearTimeout(requestID); } requestID = undefined; } }; } ExtendUtils.waitFor = waitFor; /** * Repeat interval for callback * @param callback Callback * @param miliseconds Miliseconds * @returns cancel callback */ function intervalFor(callback, miliseconds) { let requestID; if (hasRequestAnimationFrame) { let lastTime; function loop(timestamp) { // Cancelled if (requestID == null) return; if (lastTime === undefined) { lastTime = timestamp; } const elapsed = timestamp - lastTime; if (elapsed >= miliseconds) { lastTime = timestamp; callback(); } if (requestID != null) { // May also be cancelled in callback or somewhere requestID = requestAnimationFrame(loop); } } requestID = requestAnimationFrame(loop); } else { requestID = setInterval(callback, miliseconds); } return () => { if (requestID) { if (hasRequestAnimationFrame && typeof requestID === "number") { cancelAnimationFrame(requestID); } else { clearInterval(requestID); } requestID = undefined; } }; } ExtendUtils.intervalFor = intervalFor; })(ExtendUtils || (ExtendUtils = {}));