as-popup.js

'use strict';

/**
 * @class Popup_A
 * @memberof TComponents
 */
export const Popup_A = (function () {
  let _enabled = true;
  let _show = true;

  const OK = 'ok';
  const CANCEL = 'cancel';

  return {
    /**
     * @alias show
     * @memberof TComponents.Popup_A
     */
    get show() {
      return _show;
    },

    set show(value) {
      _show = value;
    },

    /**
     * @alias enabled
     * @memberof TComponents.Popup_A
     */
    get enabled() {
      return _enabled;
    },

    set enabled(value) {
      _enabled = value;
    },

    /**
     * 'ok'
     * @deprecated Uses {@link TComponents.Popup_A.OK}
     * @alias ok
     * @memberof TComponents.Popup_A
     * @static
     */
    get ok() {
      return OK;
    },

    /**
     * 'cancel'
     * @deprecated Uses {@link TComponents.Popup_A.CANCEL}
     * @alias cancel
     * @memberof TComponents.Popup_A
     * @static
     */
    get cancel() {
      return CANCEL;
    },

    /**
     * 'ok'
     * @alias OK
     * @type {string}
     * @memberof TComponents.Popup_A
     * @static
     */
    get OK() {
      return OK;
    },

    /**
     * 'cancel'
     * @alias CANCEL
     * @type {string}
     * @memberof TComponents.Popup_A
     * @static
     */
    get CANCEL() {
      return CANCEL;
    },

    /**
     * A popup dialog is a modal window that provides the user with information messages.
     * @alias message
     * @memberof TComponents.Popup_A
     * @param {string} msg1 - A string, describing the topic of the message.
     * @param {string|string[]} [msg_array] - The actual message. It can be either a simple string or,
     * if several lines are required, an array where each element is a string with a message line.
     * @param {Function(string): void} [callback] - A function that will be called when the user dismisses by pressing the 'OK' or 'Cancel' button.
     * @example
     * TComponents.Popup_A.message(
     *   "Important message!",
     *   [
     *     "For your information, this is a popup dialog.",
     *     "",
     *     "Further information can be given here!"
     *   ],
     *  function (action) {
     *    console.log("OK button was clicked")
     *  });
     */
    message: function (msg1, msg_array = [], callback = null) {
      if (!_enabled) return;

      _show ? FPComponents.Popup_A.message(msg1, msg_array, callback) : console.log(msg1, ...msg_array);
    },

    /**
     * A popup dialog is a modal window that provides the user with information messages.
     * The main difference from the Popup_A.message is that Popup_A.info includes an "i" image in the modal window.
     * @alias info
     * @memberof TComponents.Popup_A
     * @param {string} msg1 - A string, describing the topic of the message.
     * @param {string|string[]} [msg_array] - The actual message. It can be either a simple string or,
     * if several lines are required, an array where each element is a string with a message line.
     * @param {Function(string): void} [callback] - A function that will be called when the user dismisses by pressing the 'OK' or 'Cancel' button.
     * @example
     * TComponents.Popup_A.info(
     *   "Important information!",
     *   [
     *     "For your information, this is a popup dialog.",
     *     "",
     *     "Further information can be given here!"
     *   ],
     *  function (action) {
     *    console.log("OK button was clicked")
     *  });
     */
    info: function (msg1, msg_array = [], callback = null) {
      if (!_enabled) return;

      _show
        ? FPComponents.Popup_A.message(msg1, msg_array, callback, FPComponents.Popup_A.STYLE.INFORMATION)
        : console.log(msg1, ...msg_array);
    },

    /**
     * A popup dialog is a modal window that provides the user with warning messages.
     * @alias warning
     * @memberof TComponents.Popup_A
     * @param {string} msg1 - A string, describing the topic of the message.
     * @param {string|string[]} [msg_array] - The actual message. It can be either a simple string or,
     * if several lines are required, an array where each element is a string with a message line.
     * @param {Function(string): void} [callback] - A function that will be called when the user dismisses by pressing the 'OK' or 'Cancel' button.
     * @example
     *  try {
     *    // do something
     *  } catch (e) {
     *    Popup_A.waring('Something went wrong', [e.message, e.description]);
     *  }
     */
    warning: function (msg1, msg_array = [], callback = null) {
      if (!_enabled) return;

      _show
        ? FPComponents.Popup_A.message(msg1, msg_array, callback, FPComponents.Popup_A.STYLE.WARNING)
        : console.warn(msg1, ...msg_array);
    },

    /**
     * A popup dialog is a modal window that provides the user with danger messages.
     * @alias danger
     * @memberof TComponents.Popup_A
     * @param {string} msg1 - A string, describing the topic of the message.
     * @param {string|string[]} [msg_array] - The actual message. It can be either a simple string or,
     * if several lines are required, an array where each element is a string with a message line.
     * @param {Function(string): void} [callback] - A function that will be called when the user dismisses by pressing the 'OK' or 'Cancel' button.
     * @example
     *  try {
     *    // do something
     *  } catch (e) {
     *    TComponents.Popup_A.danger('Something went wrong', [e.message, e.description]);
     *  }
     */
    danger: function (msg1, msg_array = [], callback = null) {
      console.group('Popup_A.danger');
      msg1 && console.error(msg1);
      const args = Array.isArray(msg_array) ? msg_array : [msg_array];
      console.error(args);
      console.groupEnd();

      if (!_enabled) return;

      _show
        ? FPComponents.Popup_A.message(msg1, msg_array, callback, FPComponents.Popup_A.STYLE.DANGER)
        : console.error(msg1, ...msg_array);
    },

    /**
     * A popup dialog is a modal window that provides the user with error messages.
     * @alias error
     * @memberof TComponents.Popup_A
     * @param {object} e - Error object for example, errorthrown by Omnicore RWS
     * @example
     *  try {
     *    throw new Error("Whoops!");
     *  } catch (e) {
     *    TComponents.Popup_A.error(e, 'Custom title');
     *  };
     * @todo Optimize the parsing of error object
     * @see {@link https://developercenter.robotstudio.com/omnicore-sdk|Omnicore-App-SDK}
     */
    error: function (e, msg1 = '', callback = null) {
      console.group('Popup_A.error');
      msg1 && console.error(msg1);
      console.error(e);
      console.groupEnd();

      if (!_enabled) return;

      const msgArray = msg1 === '' || !e.message ? [] : [e.message];

      if (e === null || e === undefined) {
        msgArray.push('Unknown error: null or undefined');
      } else if (typeof e == 'object') {
        const entries = Object.entries(e);
        entries.forEach(([key, value]) => {
          if (key !== 'message') {
            if (Array.isArray(value)) {
              const msg = `${key}: `;
              msgArray.push(msg);
              value.forEach((element) => {
                const json = JSON.stringify(element, null, 2);

                msgArray.push(json.replace(/"([^"]+)"/g, '$1').replace(/[{}]/g, ''));
              });
            } else {
              const json = JSON.stringify(value, null, 2)
                .replace(/"([^"]+)"/g, '$1')
                .replace(/[{}]/g, '');

              const msg = `${key}: ${json}`;
              msgArray.push(msg);
            }
          }
        });
      } else if (typeof e == 'string') {
        msgArray.push(e);
      } else {
        if (typeof e === 'number' || typeof e === 'boolean') {
          msgArray.push(`Error value: ${e}`);
        } else if (typeof e === 'symbol') {
          msgArray.push(`Error symbol: ${e.toString()}`);
        } else if (typeof e === 'bigint') {
          msgArray.push(`Error bigint: ${e.toString()}`);
        } else {
          try {
            msgArray.push(e.toString());
          } catch (err) {
            msgArray.push('Unknown error: Unable to convert to string');
          }
        }
      }

      const severity =
        e.controllerStatus !== undefined && e.controllerStatus.severity === 'Error'
          ? FPComponents.Popup_A.STYLE.DANGER
          : FPComponents.Popup_A.STYLE.WARNING;

      if (msg1 === '') {
        _show
          ? FPComponents.Popup_A.message(e.message, msgArray, callback, severity)
          : console.error(e.message, ...msgArray);
      } else {
        _show ? FPComponents.Popup_A.message(msg1, msgArray, callback, severity) : console.error(msg1, ...msgArray);
      }
    },

    /**
     * A popup dialog is a modal window that provides "OK/Cancel" confirmation dialog.
     * @alias confirm
     * @memberof TComponents.Popup_A
     * @param {string} msg1 - A string, describing the topic of the message.
     * @param {string|string[]} [msg_array] - The actual message. It can be either a simple string or,
     * if several lines are required, an array where each element is a string with a message line.
     * @param {Function(string): void} [callback] - A function that will be called when the user dismisses by pressing the 'OK' or 'Cancel' button.
     * @example
     * TComponents.Popup_A.confirm(
     *   `XXX module not yet loaded on the controller`,
     *   ['This module is required for this WebApp.', 'Do you want to load the module?'],
     *   (action) => {
     *    if (action === 'ok') {
     *      console.log("Load the module here...")
     *    } else if (action == 'cancel') {
     *      console.log("Abort mission!");
     *    }
     *  }
     * );
     */
    confirm: function (msg1, msg_array, callback = null) {
      if (!_enabled) return;

      _show ? FPComponents.Popup_A.confirm(msg1, msg_array, callback) : console.log(msg1, ...msg_array);
    },

    /**
     * A popup dialog with async callback support. The popup will only close after the callback execution completes.
     * @alias asyncConfirm
     * @memberof TComponents.Popup_A
     * @param {string} header - A string, describing the header of the popup.
     * @param {string|string[]} message - The message content. Can be a string or an array of strings.
     * @param {Object} options - Configuration options
     * @param {string} [options.confirmText='OK'] - Text for the confirm button
     * @param {string} [options.cancelText='Cancel'] - Text for the cancel button
     * @param {Function(string): Promise<void>} [options.onConfirm] - Async function to execute when confirm button is clicked
     * @param {Function(string): void} [options.onCancel] - Function to execute when cancel button is clicked
     * @param {string} [options.style] - Popup style: 'information', 'warning', or 'danger'
     * @returns {Promise<string>} Returns the action ('ok' or 'cancel')
     * @example
     * await TComponents.Popup_A.asyncConfirm(
     *   'Confirm Operation',
     *   ['This will perform an async operation.', 'Do you want to continue?'],
     *   {
     *     confirmText: 'Execute',
     *     cancelText: 'Cancel',
     *     onConfirm: async (action) => {
     *       await someAsyncOperation();
     *       console.log('Operation completed');
     *     },
     *     style: 'information'
     *   }
     * );
     */
    asyncConfirm: function (header, message, options = {}) {
      if (!_enabled) {
        return Promise.resolve(CANCEL);
      }

      if (!_show) {
        return Promise.resolve(CANCEL);
      }

      const {confirmText = 'OK', cancelText = 'Cancel', onConfirm = null, onCancel = null, style = null} = options;

      return new Promise((resolve) => {
        const model = {
          header: [
            {
              type: 'text',
              text: header,
            },
          ],
          content: Array.isArray(message)
            ? message.map((line) => ({type: 'text', text: line}))
            : [{type: 'text', text: message}],
          footer: [
            {
              type: 'button',
              text: cancelText,
              action: CANCEL,
              closeDialog: true,
            },
            {
              type: 'button',
              text: confirmText,
              highlight: true,
              action: OK,
              closeDialog: false,
            },
          ],
        };

        FPComponents.Popup_A.custom(
          model,
          async (action) => {
            try {
              if (action === OK && typeof onConfirm === 'function') {
                await onConfirm(action);
              } else if (action === CANCEL && typeof onCancel === 'function') {
                await onCancel(action);
              }
            } catch (error) {
              console.error('Error in asyncConfirm callback:', error);
            } finally {
              FPComponents.Popup_A.close(action);
              resolve(action);
            }
          },
          style ? FPComponents.Popup_A.STYLE[style.toUpperCase()] : null,
        );
      });
    },
  };
})();