/** Helper functions for checking types and simplifying logging/error handling. */ const isNumber = x => typeof x === "number" && isFinite(x); const isNumberLogError = x => { if (isNumber(x)) { return true; } console.error("expected number, got:", typeof x); return false; } const isNumberThrowError = x => { if (isNumber(x)) { return; } throw new Error("expected number, got:", typeof x); } const isString = x => typeof x === "string" || x instanceof String; const isStringLogError = x => { if (isString(x)) { return true; } console.error("expected string, got:", typeof x); return false; }; const isStringThrowError = x => { if (isString(x)) { return; } throw new Error("expected string, got:", typeof x); }; const isNull = x => x === null; const isUndefined = x => typeof x === "undefined" || x === undefined; // checks both null and undefined for simplicity sake. const isNullOrUndefined = x => isNull(x) || isUndefined(x); const isNullOrUndefinedLogError = x => { if (isNullOrUndefined(x)) { console.error("Variable is null/undefined."); return true; } return false; }; const isNullOrUndefinedThrowError = x => { if (!isNullOrUndefined(x)) { return; } throw new Error("Variable is null/undefined."); }; const isElement = x => x instanceof Element; const isElementLogError = x => { if (isElement(x)) { return true; } console.error("expected element type, got:", typeof x); return false; }; const isElementThrowError = x => { if (isElement(x)) { return; } throw new Error("expected element type, got:", typeof x); }; const isFunction = x => typeof x === "function"; const isFunctionLogError = x => { if (isFunction(x)) { return true; } console.error("expected function type, got:", typeof x); return false; }; const isFunctionThrowError = x => { if (isFunction(x)) { return; } throw new Error("expected function type, got:", typeof x); }; const getElementByIdLogError = selector => { const elem = gradioApp().getElementById(selector); isElementLogError(elem); return elem; }; const getElementByIdThrowError = selector => { const elem = gradioApp().getElementById(selector); isElementThrowError(elem); return elem; }; const querySelectorLogError = selector => { const elem = gradioApp().querySelector(selector); isElementLogError(elem); return elem; }; const querySelectorThrowError = selector => { const elem = gradioApp().querySelector(selector); isElementThrowError(elem); return elem; }; /** Functions for getting dimensions of elements. */ const getComputedPropertyDims = (elem, prop) => { /** Returns the top/left/bottom/right float dimensions of an element for the specified property. */ const style = window.getComputedStyle(elem, null); return { top: parseFloat(style.getPropertyValue(`${prop}-top`)), left: parseFloat(style.getPropertyValue(`${prop}-left`)), bottom: parseFloat(style.getPropertyValue(`${prop}-bottom`)), right: parseFloat(style.getPropertyValue(`${prop}-right`)), }; }; const getComputedMarginDims = elem => { /** Returns the width/height of the computed margin of an element. */ const dims = getComputedPropertyDims(elem, "margin"); return { width: dims.left + dims.right, height: dims.top + dims.bottom, }; }; const getComputedPaddingDims = elem => { /** Returns the width/height of the computed padding of an element. */ const dims = getComputedPropertyDims(elem, "padding"); return { width: dims.left + dims.right, height: dims.top + dims.bottom, }; }; const getComputedBorderDims = elem => { /** Returns the width/height of the computed border of an element. */ // computed border will always start with the pixel width so thankfully // the parseFloat() conversion will just give us the width and ignore the rest. // Otherwise we'd have to use border--width instead. const dims = getComputedPropertyDims(elem, "border"); return { width: dims.left + dims.right, height: dims.top + dims.bottom, }; }; const getComputedDims = elem => { /** Returns the full width and height of an element including its margin, padding, and border. */ const width = elem.scrollWidth; const height = elem.scrollHeight; const margin = getComputedMarginDims(elem); const padding = getComputedPaddingDims(elem); const border = getComputedBorderDims(elem); return { width: width + margin.width + padding.width + border.width, height: height + margin.height + padding.height + border.height, }; }; const calcColsPerRow = function(parent, child) { /** Calculates the number of columns of children that can fit in a parent's visible width. */ const parent_inner_width = parent.offsetWidth - getComputedPaddingDims(parent).width; return parseInt(parent_inner_width / getComputedDims(child).width); }; const calcRowsPerCol = function(parent, child) { /** Calculates the number of rows of children that can fit in a parent's visible height. */ const parent_inner_height = parent.offsetHeight - getComputedPaddingDims(parent).height; return parseInt(parent_inner_height / getComputedDims(child).height); }; /** Functions for asynchronous operations. */ const debounce = (handler, timeout_ms) => { /** Debounces a function call. * * NOTE: This will NOT work if called from within a class. * It will drop `this` from scope. * * Repeated calls to the debounce handler will not call the handler until there are * no new calls to the debounce handler for timeout_ms time. * * Example: * function add(x, y) { return x + y; } * let debounce_handler = debounce(add, 5000); * let res; * for (let i = 0; i < 10; i++) { * res = debounce_handler(i, 100); * } * console.log("Result:", res); * * This example will print "Result: 109". */ let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => handler(...args), timeout_ms); }; }; const waitForElement = selector => { /** Promise that waits for an element to exist in DOM. */ return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); }); }; const waitForBool = o => { /** Promise that waits for a boolean to be true. * * `o` must be an Object of the form: * { state: } * * Resolves when (state === true) */ return new Promise(resolve => { (function _waitForBool() { if (o.state) { return resolve(); } setTimeout(_waitForBool, 100); })(); }); }; const waitForKeyInObject = o => { /** Promise that waits for a key to exist in an object. * * `o` must be an Object of the form: * { * obj: , * k: , * } * * Resolves when (k in obj) */ return new Promise(resolve => { (function _waitForKeyInObject() { if (o.k in o.obj) { return resolve(); } setTimeout(_waitForKeyInObject, 100); })(); }); }; const waitForValueInObject = o => { /** Promise that waits for a key value pair in an Object. * * `o` must be an Object of the form: * { * obj: , * k: , * v: * } * * Resolves when obj[k] == v */ return new Promise(resolve => { waitForKeyInObject({k: o.k, obj: o.obj}).then(() => { (function _waitForValueInObject() { if (o.k in o.obj && o.obj[o.k] == o.v) { return resolve(); } setTimeout(_waitForValueInObject, 100); })(); }); }); }; /** Misc helper functions. */ const clamp = (x, min, max) => Math.max(min, Math.min(x, max)); const getStyle = (prop, elem) => { return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; } const htmlStringToElement = function(str) { /** Converts an HTML string into an Element type. */ let parser = new DOMParser(); let tmp = parser.parseFromString(str, "text/html"); return tmp.body.firstElementChild; };