UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

319 lines (318 loc) 12.1 kB
/** * The TimerManagement class is used to track a set of timers. */ export class TimerManagement { constructor(parent) { this.disposed = false; this.immediateIds = null; this.intervals = []; this.timeouts = []; this.parent = parent || null; } /** * clearAllTimers is used to clear any active timers in the object. */ clearAllTimers() { for (const intervalId of this.intervals) { window.clearInterval(intervalId); } for (const timeoutId of this.timeouts) { window.clearTimeout(timeoutId); } this.intervals.splice(0, this.intervals.length); this.timeouts.splice(0, this.timeouts.length); } /** * Clears the immediate. * @param id - Id to cancel. */ clearImmediate(id) { if (this.immediateIds && this.immediateIds[id]) { window.clearTimeout(id); delete this.immediateIds[id]; } } /** * clearInterval is used to stop the series of callbacks that was setup through setInterval. * * @param intervalId - The id returned from eh setInterval call that you want stopped. */ clearInterval(intervalId) { window.clearInterval(intervalId); this.removeInterval(intervalId); } /** * clearTimeout is used to stop a timeout callback that was setup through setTimeout. * * @param timeoutId - The id returned from the setTimeout call that you want stopped. */ clearTimeout(timeoutId) { window.clearTimeout(timeoutId); this.removeTimeout(timeoutId); } /** * SetImmediate override, which will auto cancel the immediate during dispose. * @param callback - Callback to execute. * @returns The setTimeout id. */ setImmediate(callback) { let immediateId = 0; if (!this.disposed) { if (!this.immediateIds) { this.immediateIds = {}; } const setImmediateCallback = () => { // Time to execute the timeout, enqueue it as a foreground task to be executed. try { // Now delete the record and call the callback. if (this.immediateIds) { delete this.immediateIds[immediateId]; } callback.apply(this.parent); } catch (e) { } }; immediateId = window.setTimeout(setImmediateCallback, 0); this.immediateIds[immediateId] = true; } return immediateId; } /** * setInterval is used to setup a callback that is called on an interval. * * @param callback - The callback that should be called each interval time period. * * @param milliseconds - The number of milliseconds between each callback. * * @param args - Optional variable argument list passed to the callback. * * @returns - returns a handle to the interval, this can be used to cancel through clearInterval method. */ setInterval(callback, milliseconds, ...args) { // Create the timer, and add a method to track the completion so we can // remove our tracked reference. const intervalId = window.setInterval(callback, milliseconds, ...args); this.intervals.push(intervalId); return intervalId; } /** * setTimeout is used to setup a onetime callback that is called after the specified timeout. * * @param callback - The callback that should be called when the time period has elapsed. * * @param milliseconds - The number of milliseconds before the callback should be called. * Even if a timeout of 0 is used the callback will be executed asynchronouly. * * @param args - Optional variable argument list passed to the callback. * * @returns - returns a handle to the timeout, this can be used to cancel through clearTimeout method. */ setTimeout(callback, milliseconds, ...args) { let timeoutId = 0; // Create the timer, and add a method to track the completion so we can // remove our tracked reference. timeoutId = window.setTimeout(() => { this.removeTimeout(timeoutId); callback(...args); }, milliseconds, ...args); this.timeouts.push(timeoutId); return timeoutId; } dispose() { this.disposed = true; this.parent = null; this.clearAllTimers(); // Clear immediates. if (this.immediateIds) { for (const id in this.immediateIds) { if (this.immediateIds.hasOwnProperty(id)) { this.clearImmediate(parseInt(id, 10)); } } } this.immediateIds = null; } /** * Creates a function that will delay the execution of func until after wait milliseconds have * elapsed since the last time it was invoked. Provide an options object to indicate that func * should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls * to the debounced function will return the result of the last func call. * * Note: If leading and trailing options are true func will be called on the trailing edge of * the timeout only if the the debounced function is invoked more than once during the wait * timeout. * * @param func - The function to debounce. * @param wait - The number of milliseconds to delay. * @param options - The options object. * @returns The new debounced function. */ debounce(func, wait, options) { if (this.disposed) { const noOpFunction = (() => { /** Do nothing */ }); noOpFunction.cancel = () => { return; }; noOpFunction.flush = (() => null); noOpFunction.pending = () => false; return noOpFunction; } const waitMS = wait || 0; let leading = false; let trailing = true; let maxWait = null; let lastCallTime = 0; let lastExecuteTime = new Date().getTime(); let lastResult; let lastArgs; let timeoutId = null; if (options) { leading = options.leading || false; trailing = options.trailing || true; maxWait = options.maxWait || null; } const markExecuted = (time) => { if (timeoutId) { this.clearTimeout(timeoutId); timeoutId = null; } lastExecuteTime = time; }; const invokeFunction = (time) => { markExecuted(time); lastResult = func.apply(null, lastArgs); }; const callback = (userCall) => { const now = new Date().getTime(); let executeImmediately = false; if (userCall) { if (leading && now - lastCallTime >= waitMS) { executeImmediately = true; } lastCallTime = now; } const delta = now - lastCallTime; let waitLength = waitMS - delta; const maxWaitDelta = now - lastExecuteTime; let maxWaitExpired = false; if (maxWait !== null) { // maxWait only matters when there is a pending callback if (maxWaitDelta >= maxWait && timeoutId) { maxWaitExpired = true; } else { waitLength = Math.min(waitLength, maxWait - maxWaitDelta); } } if (delta >= waitMS || maxWaitExpired || executeImmediately) { invokeFunction(now); } else if ((timeoutId === null || !userCall) && trailing) { timeoutId = this.setTimeout(callback, waitLength); } return lastResult; }; const pending = () => { return !!timeoutId; }; const cancel = () => { if (pending()) { // Mark the debounced function as having executed markExecuted(new Date().getTime()); } }; const flush = () => { if (pending()) { invokeFunction(new Date().getTime()); } return lastResult; }; // tslint:disable-next-line:no-any const resultFunction = ((...args) => { lastArgs = args; return callback(true); }); resultFunction.cancel = cancel; resultFunction.flush = flush; resultFunction.pending = pending; return resultFunction; } /** * Creates a function that, when executed, will only call the func function at most once per * every wait milliseconds. Provide an options object to indicate that func should be invoked * on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled * function will return the result of the last func call. * * Note: If leading and trailing options are true func will be called on the trailing edge of * the timeout only if the the throttled function is invoked more than once during the wait timeout. * * @param func - The function to throttle. * @param wait - The number of milliseconds to throttle executions to. Defaults to 0. * @param options - The options object. * @returns The new throttled function. */ throttle(func, wait, options) { if (this.disposed) { const noOpFunction = (() => { /** Do nothing */ }); noOpFunction.cancel = () => { return; }; noOpFunction.flush = (() => null); noOpFunction.pending = () => false; return noOpFunction; } const waitMS = wait || 0; let leading = true; let trailing = true; let lastExecuteTime = 0; let lastResult; // tslint:disable-next-line:no-any let lastArgs; let timeoutId = null; if (options && typeof options.leading === "boolean") { leading = options.leading; } if (options && typeof options.trailing === "boolean") { trailing = options.trailing; } const callback = (userCall) => { const now = new Date().getTime(); const delta = now - lastExecuteTime; const waitLength = leading ? waitMS - delta : waitMS; if (delta >= waitMS && (!userCall || leading)) { lastExecuteTime = now; if (timeoutId) { this.clearTimeout(timeoutId); timeoutId = null; } lastResult = func.apply(null, lastArgs); } else if (timeoutId === null && trailing) { timeoutId = this.setTimeout(callback, waitLength); } return lastResult; }; // tslint:disable-next-line:no-any const resultFunction = (...args) => { lastArgs = args; return callback(true); }; return resultFunction; } removeInterval(intervalId) { const index = this.intervals.indexOf(intervalId); if (index >= 0) { this.intervals.splice(index, 1); } } removeTimeout(timeoutId) { const index = this.timeouts.indexOf(timeoutId); if (index >= 0) { this.timeouts.splice(index, 1); } } }