@etsoo/shared
Version:
TypeScript shared utilities and functions
198 lines (197 loc) • 6.58 kB
JavaScript
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 = {}));