All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.primefaces-extensions.monacoeditor.04-monaco-widget-framed.js Maven / Gradle / Ivy

There is a newer version: 14.0.7.1
Show newest version
// @ts-check

window.monacoModule = window.monacoModule || {};

(function () {

  const {
    FramedDiffEditorDefaults,
    FramedEditorDefaults,
    getFacesResourceUri,
    getMonacoResource,
    isMonacoMessage,
    resolveLocaleUrl,
  } = window.monacoModule.helper;
  const ExtMonacoEditorBase = window.monacoModule.ExtMonacoEditorBase;

  let InstanceId = 0;

  let MessageId = 0;

  /**
   * @template {import("monaco-editor").editor.IEditor} TEditor
   * @template {import("monaco-editor").editor.IEditorOptions} TEditorOpts
   * @template {PrimeFaces.widget.ExtMonacoEditor.AsyncMonacoBaseEditorContext} TContext
   * @template {PrimeFaces.widget.ExtMonacoEditor.ExtenderBaseEditor} TExtender
   * @template {PrimeFaces.widget.ExtMonacoBaseEditorFramedCfg & PrimeFaces.widget.ExtMonacoFramedEditorCfgBase} TCfg
   * @extends {ExtMonacoEditorBase}
   */
  class FramedBaseImpl extends ExtMonacoEditorBase {
    /**
     * @param  {PrimeFaces.PartialWidgetCfg} cfg Arguments as passed by PrimeFaces.
     */
    constructor(cfg) {
      super(cfg);

      /** @type {number | undefined} */
      this._instanceId;

      /** @type {Map void, reject: (error: string) => void}> | undefined} */
      this._responseMap;

      /** @type {string | undefined} */
      this._resolvedLocaleUrl;

      /** @type {((event: MessageEvent) => void) | undefined} */
      this._messageListener;
    }

    /**
     * @param {TCfg} cfg
     */
    init(cfg) {
      super._init(cfg, this._getConfigDefaults());

      this.addRefreshListener(() => this._onRefresh());
      this.addDestroyListener(() => this._onDestroy());

      this._instanceId = ++InstanceId;

      this._responseMap = new Map();

      this._resolvedLocaleUrl = resolveLocaleUrl(this.cfg);

      // Initialize iframe
      const iframeUrl = getMonacoResource("iframe.html", {
        bootstrap: [
          this.cfg.extender ? this.cfg.extender : "",
          this._resolvedLocaleUrl,
          getMonacoResource("editor.js"),
          getMonacoResource("iframe-controller.js")
        ],
        instanceId: this._instanceId,
        editorType: this._getEditorType(),
        ...this.cfg.iframeUrlParams,
      });

      this._getIframe().src = iframeUrl;

      // Communicate with iframe
      if (this._messageListener) {
        window.removeEventListener("message", this._messageListener);
      }
      this._messageListener = event => this._onMessage(event);
      window.addEventListener("message", this._messageListener);
    }

    // === PUBLIC API, see monaco-editor.d.ts

    /**
     * @returns {Promise}
     */
    async layout() {
      if (this.isReady()) {
        return this._invokeLayout();
      }
      else {
        return undefined;
      }
    }

    /**
     * @template {PrimeFaces.MatchingKeys unknown>} K
     * @param {K} method
     * @param {PrimeFaces.widget.ExtMonacoEditor.ParametersIfFn} args
     * @returns {Promise>>}
     */
    async invokeMonaco(method, ...args) {
      if (!this.isReady()) {
        throw new Error(`Cannot invoke monaco as the editor is not ready yey. Use isReady / whenReady to check.`);
      }
      return this._invokeMonaco(method, ...args);
    }

    /**
     * @template TRetVal
     * @template {any[]} TArgs
     * @param {((editor: TEditor, ...args: TArgs) => TRetVal) | string} script
     * @param {TArgs} args
     * @returns {Promise>}
     */
    async invokeMonacoScript(script, ...args) {
      if (!this.isReady()) {
        throw new Error(`Cannot invoke monaco as the editor is not ready yey. Use isReady / whenReady to check.`);
      }
      return this._postPromise({
        kind: "invokeMonacoScript",
        data: {
          args,
          messageId: this._createMessageId(),
          script: script.toString(),
        },
      });
    }

    /**
     * @returns {Promise}
     */
    async getValue() {
      if (this.isReady()) {
        return this._invokeGetValue();
      }
      else {
        return this._editorValue || "";
      }
    }

    /**
     * @param {string} value
     * @returns {Promise}
     */
    async setValue(value) {
      if (this.isReady()) {
        return this._invokeSetValue(value);
      }
      else {
        this._editorValue = value;
        return undefined;
      }
    }

    // === PROTECTED

    /**
     * @abstract
     * @protected
     * @returns {string}
     */
    _getEditorType() {
      throw new Error("Must override abstract method");
    }

    /**
     * @abstract
     * @protected
     * @returns {Partial}
     */
    _getConfigDefaults() {
      throw new Error("Must override abstract method");
    }

    /**
     * @abstract
     * @protected
     * @returns {Promise}
     */
    _invokeLayout() {
      throw new Error("Must override abstract method");
    }

    /**
     * @abstract
     * @protected
     * @returns {Promise}
     */
    _invokeGetValue() {
      throw new Error("Must override abstract method");
    }

    /**
     * @protected
     * @template {PrimeFaces.MatchingKeys unknown>} K
     * @param {K} method
     * @param {PrimeFaces.widget.ExtMonacoEditor.ParametersIfFn} args
     * @returns {Promise>>}
     */
    _invokeMonaco(method, ...args) {
      throw new Error("Must override abstract method");
    }

    /**
     * @abstract
     * @protected
     * @param {string} value
     * @returns {Promise}
     */
    _invokeSetValue(value) {
      throw new Error("Must override abstract method");
    }

    /**
     * @abstract
     * @protected
     * @param {InitBaseMessageData} initMessageData
     * @param {Partial>} cloneableCfg
     */
    _postInit(initMessageData, cloneableCfg) {
      throw new Error("Must override abstract method");
    }

    /**
     * @protected
     * @param {MonacoMessage} message
     */
    _handleMessage(message) {
      console.warn("[MonacoEditor] Unhandled message:", message);
    }

    /**
     * @protected
     * @returns {number}
     */
    _createMessageId() {
      const messageId = ++MessageId;
      return messageId;
    }

    // === PRIVATE

    _bindEvents() {
      this._postMessage({
        kind: "bindEvents",
        data: {},
      });
    }

    _render() {
      const cloneableCfg = this._createCloneableCfg();
      this._postInit({
        facesResourceUri: getFacesResourceUri(),
        id: this.getWidgetId(),
        nonce: PrimeFaces.csp.NONCE_VALUE,
        resolvedLocaleUrl: this._resolvedLocaleUrl || "",
        resourceUrlExtension: PrimeFaces.resources.getResourceUrlExtension(),
        scrollTop: this._scrollTop || 0,
        supportedEvents: this._listSupportedEvents(),
        value: this._editorValue || "",
        version: PrimeFacesExt.VERSION,
      }, cloneableCfg);
    }

    /**
     * @private
     */
    _onRefresh() {
      this._onDestroy();
    }

    /**
     * @private
     */
    _onDestroy() {
      this._rejectOnDone();
      this._postMessage({
        kind: "destroy",
        data: undefined,
      });
      if (this._messageListener) {
        window.removeEventListener("message", this._messageListener);
        this._messageListener = undefined;
      }
    }

    /**
     * @private
     * @returns {HTMLIFrameElement}
     */
    _getIframe() {
      const iframe = this.getEditorContainer().get(0);
      if (!iframe) {
        throw new Error("[MonacoEditor] Editor container does not exist");
      }
      if (!(iframe instanceof HTMLIFrameElement)) {
        throw new Error("[MonacoEditor] Expected editor container to be an iframe, but got: " + iframe.outerHTML);
      }
      return iframe;
    }

    /**
     * A subset of the widget configuration that can be sent to the iframe.
     * @private
     * @returns {Partial>}
     */
    _createCloneableCfg() {
      /** @type {Record} */
      const clone = {};
      /** @type {Record} */
      const original = this.cfg;
      for (const key of Object.keys(original)) {
        const value = original[key];
        if (key !== "behaviors" && typeof value !== "function") {
          clone[key] = value;
        }
      }
      return /** @type {Partial>} */(clone);
    }

    // === MESSAGING

    /**
     * Post a message to the monaco editor iframe via `postMessage`, and get a
     * promise that resolves with the response.
     * @protected
     * @param {MonacoMessagePayload & {data: {messageId: number}}} message
     * @returns {Promise}
     */
    async _postPromise(message) {
      const error = this._postMessage(message);
      if (error !== undefined) {
        return Promise.reject(new Error(error));
      }
      return new Promise((resolve, reject) => {
        this._responseMap = this._responseMap || new Map();
        this._responseMap.set(message.data.messageId, { resolve, reject });
      });
    }

    /**
     * Post a message to the monaco editor iframe via `postMessage`.
     * @protected
     * @param {MonacoMessagePayload} payload
     * @returns {string | undefined} Error message when message could not be posted.
     */
    _postMessage(payload) {
      if (this._instanceId === undefined) {
        // Cannot happen normally since init is called immediately upon construction
        throw new Error("IllegalState: Framed widget has no instance ID");
      }
      const iframe = this._getIframe();
      if (!iframe || !iframe.contentWindow) {
        return "Cannot not post message, iframe not yet loaded";
      }
      /** @type {MonacoMessage} */
      const message = {
        instanceId: this._instanceId,
        payload,
      };
      iframe.contentWindow.postMessage(message, window.location.href);
      return undefined;
    }

    /**
     * Callback when the iframe sends a message via `postMessage`
     * @private
     * @param {MessageEvent} event
     */
    _onMessage(event) {
      if (isMonacoMessage(event.data)) {
        const message = event.data;
        if (this._instanceId === undefined) {
          throw new Error("IllegalState: Framed widget has no instance ID");
        }
        if (this._instanceId >= 0 && message.instanceId >= 0 && this._instanceId !== message.instanceId) {
          // this is normal when there are multiple editors all sending messages
          return;
        }
        switch (message.payload.kind) {
          case "load":
            this._onMessageLoad();
            break;
          case "response":
            this._onMessageResponse(message.payload.data.messageId, message.payload.data);
            break;
          case "valueChange":
            this._onMessageValueChange(message.payload.data);
            break;
          case "scrollChange":
            this._onMessageScrollChange(message.payload.data);
            break;
          case "domEvent":
            this._onMessageDomEvent(message.payload.data);
            break;
          case "afterInit":
            this._onMessageAfterInit(message.payload.data);
            break;
          default:
            this._handleMessage(message);
            break;
        }
      }
    }

    /**
     * Called when the iframe document is ready.
     * @private
     */
    _onMessageLoad() {
      this.renderDeferred();
    }

    /**
     * Called when the iframe sends a response to a message.
     * @private
     * @param {number} messageId
     * @param {ResponseMessageData} data
     */
    _onMessageResponse(messageId, data) {
      this._responseMap = this._responseMap || new Map();
      const entry = this._responseMap.get(messageId);
      if (entry !== undefined) {
        const { resolve, reject } = entry;
        this._responseMap.delete(messageId);
        if (data.success === true) {
          resolve(data.value);
        }
        else if (data.success === false) {
          reject(data.error);
        }
      }
    }

    /**
     * Called when the value of the monaco editor in the iframe has changed.
     * @private
     * @param {ValueChangeMessageData} data
     */
    _onMessageValueChange(data) {
      this.getInput().val(data.value || "");
      this._fireEvent("change", data.changes);
    }

    /**
     * Called when the scroll position of the monaco editor in the iframe has changed.
     * @private
     * @param {ScrollChangeMessageData} data
     */
    _onMessageScrollChange(data) {
      this._scrollTop = data.scrollTop;
    }

    /**
     * Called when a DOM even such as `blur` or `keyup` was triggered on the
     * monaco editor in the iframe.
     * @private
     * @param {DomEventMessageData} data
     */
    _onMessageDomEvent(data) {
      const args = data.data ? JSON.parse(data.data) : [];
      this._fireEvent(data.name, ...args);
    }

    /**
     * Called after the monaco editor in the iframe was initialized, and also
     * in case an error occurred.
     * @private
     * @param {AfterInitMessageData} data
     */
    _onMessageAfterInit(data) {
      if (data.success === true) {
        this._onInitSuccess();
      }
      else if (data.success === false) {
        this._onInitError(data.error);
      }
    }
  }

  /**
   * @extends {FramedBaseImpl}
   */
  class FramedEditorImpl extends FramedBaseImpl {
    /**
     * @param  {PrimeFaces.PartialWidgetCfg} cfg Arguments as passed by PrimeFaces.
     */
    constructor(cfg) {
      super(cfg);
    }

    // === PUBLIC API, see monaco-editor.d.ts

    // === PROTECTED

    /**
     * @protected
     * @returns {string}
     */
    _getEditorType() {
      return "codeEditor";
    }

    /**
     * @protected
     * @returns {Partial}
     */
    _getConfigDefaults() {
      return FramedEditorDefaults;
    }

    /**
     * @protected
     * @param {InitBaseMessageData} initMessageData
     * @param {Partial} cloneableCfg
     */
    _postInit(initMessageData, cloneableCfg) {
      this._postMessage({
        kind: "init",
        data: {
          ...initMessageData,
          options: cloneableCfg,
        },
      });
    }

    /**
     * @protected
     * @returns {Promise}
     */
    async _invokeGetValue() {
      return this.invokeMonaco("getValue");
    }

    /**
     * @protected
     * @returns {Promise}
     */
    async _invokeLayout() {
      return this.invokeMonaco("layout")
    }

    /**
     * @protected
     * @template {PrimeFaces.MatchingKeys unknown>} K
     * @param {K} method
     * @param {PrimeFaces.widget.ExtMonacoEditor.ParametersIfFn} args
     * @returns {Promise>>}
     */
    async _invokeMonaco(method, ...args) {
      return this._postPromise({
        kind: "invokeMonaco",
        data: {
          args,
          messageId: this._createMessageId(),
          method,
        },
      });
    }

    /**
     * @protected
     * @param {string} value
     * @returns {Promise}
     */
    async _invokeSetValue(value) {
      return this.invokeMonaco("setValue", value);
    }
  }

  /**
   * @extends {FramedBaseImpl}
   */
  class FramedDiffEditorImpl extends FramedBaseImpl {
    /**
     * @param  {PrimeFaces.PartialWidgetCfg} cfg Arguments as passed by PrimeFaces.
     */
    constructor(cfg) {
      super(cfg);

      /** @type {string | undefined} */
      this._originalEditorValue;

      /** @type {JQuery | undefined} */
      this._originalInput;

      /** @type {number | undefined} */
      this._originalScrollTop;
    }

    /**
     * @param {PrimeFaces.widget.ExtMonacoDiffEditorFramedCfg} cfg 
     */
    init(cfg) {
      super.init(cfg);

      // Textarea with the value for the original editor
      this._originalInput = this.jq.find(".ui-helper-hidden-accessible textarea").eq(1);

      // Default to the given value for the original editor
      this._originalEditorValue = String(this._originalInput.val() || "");
    }

    // === PUBLIC API, see monaco-editor.d.ts

    /**
     * @returns {JQuery}
     */
    getOriginalInput() {
      return this._originalInput || $();
    }

    /**
     * @returns {Promise}
     */
    async getOriginalValue() {
      if (this.isReady()) {
        return this.invokeMonacoScript(editor => editor.getOriginalEditor().getValue());
      }
      else {
        return this._originalEditorValue || "";
      }
    }

    /**
     * @param {string} value 
     * @returns {Promise}
     */
    async setOriginalValue(value) {
      if (this.isReady()) {
        return this.invokeMonacoScript((editor, val) => editor.getOriginalEditor().setValue(val), value);
      }
      else {
        this._originalEditorValue = value;
        return undefined;
      }
    }

    // === PROTECTED

    /**
     * @protected
     * @returns {string}
     */
    _getEditorType() {
      return "diffEditor";
    }

    /**
     * @protected
     * @returns {Partial}
     */
    _getConfigDefaults() {
      return FramedDiffEditorDefaults;
    }

    /**
     * @protected
     * @param {InitBaseMessageData} initMessageData
     * @param {Partial} cloneableCfg
     */
    _postInit(initMessageData, cloneableCfg) {
      this._postMessage({
        kind: "initDiff",
        data: {
          ...initMessageData,
          options: cloneableCfg,
          originalScrollTop: this._originalScrollTop || 0,
          originalValue: this._originalEditorValue || "",
        },
      });
    }

    /**
     * @protected
     * @returns {Promise}
     */
    async _invokeGetValue() {
      return this.invokeMonacoScript(editor => editor.getModifiedEditor().getValue());
    }

    /**
     * @protected
     * @returns {Promise}
     */
    async _invokeLayout() {
      return this.invokeMonaco("layout")
    }

    /**
     * @protected
     * @template {PrimeFaces.MatchingKeys unknown>} K
     * @param {K} method
     * @param {PrimeFaces.widget.ExtMonacoEditor.ParametersIfFn} args
     * @returns {Promise>>}
     */
    async _invokeMonaco(method, ...args) {
      return this._postPromise({
        kind: "invokeMonacoDiff",
        data: {
          args,
          messageId: this._createMessageId(),
          method,
        },
      });
    }

    /**
     * @protected
     * @param {string} value
     * @returns {Promise}
     */
    async _invokeSetValue(value) {
      return this.invokeMonacoScript((editor, val) => editor.getModifiedEditor().setValue(val), value);
    }

    // === MESSAGING

    /**
     * @protected
     * @param {MonacoMessage} message
     */
    _handleMessage(message) {
      switch (message.payload.kind) {
        case "originalScrollChange":
          this._onMessageOriginalScrollChange(message.payload.data);
          break;
        case "originalValueChange":
          this._onMessageOriginalValueChange(message.payload.data);
          break;
        default:
          console.warn("[MonacoEditor] Unhandled message:", message);
          break;
      }
    }

    /**
     * Called when the scroll position of the original Monaco editor in the iframe has changed.
     * @private
     * @param {ScrollChangeMessageData} data
     */
    _onMessageOriginalScrollChange(data) {
      this._originalScrollTop = data.scrollTop;
    }

    /**
     * Called when the value of the original Monaco editor in the iframe has changed.
     * @private
     * @param {ValueChangeMessageData} data
     */
    _onMessageOriginalValueChange(data) {
      this.getOriginalInput().val(data.value || "");
      this._fireEvent("originalChange", data.changes);
    }
  }

  PrimeFaces.widget.ExtMonacoBaseEditorFramed = FramedBaseImpl;
  PrimeFaces.widget.ExtMonacoCodeEditorFramed = FramedEditorImpl;
  PrimeFaces.widget.ExtMonacoDiffEditorFramed = FramedDiffEditorImpl;

  // TODO remove in one of the next major releases
  // @ts-expect-error legacy, will be removed soon
  PrimeFaces.widget.ExtMonacoEditorFramed
    = FramedEditorImpl;
})();




© 2015 - 2024 Weber Informatics LLC | Privacy Policy