AsyncUI Class

UI utility class

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/476649/1259544/AsyncUI%20Class.js

// ==UserScript==
// @name         AsyncUI Class
// @version      1.0.0
// @description  UI utility class
// @author       Paweł Malak (pawemala) LCJ2
// @license      MIT
// ==/UserScript==

class AsyncUI {
  /**
   * Wait for an element with a given selector to be available
   * @param {string} selector CSS selector of an element
   * @returns {Promise<HTMLElement>}
   */
  static waitForElement(selector) {
    return new Promise(resolve => {
      if (document.querySelector(selector)) {
        return resolve(document.querySelector(selector));
      }

      const observer = new MutationObserver(() => {
        if (document.querySelector(selector)) {
          resolve(document.querySelector(selector));
          observer.disconnect();
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
    });
  }

  /**
   * Wait n seconds for an element. Return element if exists or null if not
   * @param {string} selector CSS selector of an element
   * @param {number} timeout Time to wait for element in seconds
   * @returns {Promise<HTMLElement | null>} Element if exists or null if not
   */
  static timeForElement(selector, timeout = 10) {
    return new Promise(resolve => {
      if (document.querySelector(selector)) {
        return resolve(document.querySelector(selector));
      }

      const startTime = Date.now();
      timeout *= 1000;

      const wait = setInterval(() => {
        if (document.querySelector(selector)) {
          window.clearInterval(wait);
          return resolve(document.querySelector(selector));
        }

        if (Date.now() - startTime > timeout) {
          window.clearInterval(wait);
          return resolve(null);
        }
      }, 1000);
    });
  }

  /**
   * Watch element and execute callback function on change
   * @param {string} selector CSS selector of an element
   * @param {Function} callback Function to be called upon change detection
   * @returns {MutationObserver} Observer object to disconnect when it's no longer needed
   */
  static watchForChanges(selector, callback) {
    if (!document.querySelector(selector)) {
      throw new Error(`${selector} was not found`);
    }

    const observer = new MutationObserver(callback);

    observer.observe(document.querySelector(selector), {
      attributes: true,
      childList: true,
      subtree: true
    });

    return observer;
  }

  /**
   * Inject custom CSS styles at the bottom of <head> element
   * @param {string} style Custom CSS string to be injected
   */
  static injectStyle(style) {
    const customStyleEl = document.createElement('style');
    customStyleEl.textContent = style;
    document.head.appendChild(customStyleEl);
  }
}