Preview External Window Disappears

I am attempting to assist with what was spoken about in issue #48326 preview external window disappears. The goal is if the user chose to have a preview window open it should not disappear, nor should a new window open when advancing in the curriculum, but rather the same window should stay open and refresh with the data from the next challenge. additionally, as mentioned in the comments, I will close the window on leaving the challenges.

I am trying to implement the advise given in the issue but I think I am having difficulty in the saga.

on page load I get:

react-dom.development.js:237 Uncaught RangeError: Maximum call stack size exceeded.
    at Object.invokeGuardedCallbackDev

and when I try to open the preview:

TypeError: Cannot read properties of undefined (reading 'createElement')

I guess it is because it was expecting a document but now it is recieving a window:

line 90 in previewPortal:
this.props.storeportalWindow(this.externalWindow);

Please advise what is causing these two errors and how I can go about fixing them? Thank you.

work done so far:
https://github.com/KravMaguy/freeCodeCamp/tree/fix/close-external-window-only-on-leave-challenges

edit: actully, I think I just solved it, will push changes…

Hey @kravmaguy Are you still struggling with this?

thank you for replying,

Summary:
Keep the same functionality as currently exists. Only, on advancing and moving backwards in the curriculum, if the portalWindow is open, keep it open and populate it with the new content. If going to another page non related to challenges (settings for example), close the portal window.

Please confirm that the advise given in the issue is correct :

Starting over, here’s the setup Ive done so far (working as expected):

first commit

I would also like to convert the portal-preview into a functional component but it is throwing many console errors and the window is not being filled with the props.children:

import { ReactElement, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { TFunction, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { storePortalDocument, removePortalDocument } from '../redux/actions';
import { portalDocumentSelector } from '../redux/selectors';

interface PreviewPortalProps {
  children: ReactElement | null;
  togglePane: (pane: string) => void;
  windowTitle: string;
  t: TFunction;
  storePortalDocument: (document: Document | undefined) => void;
  removePortalDocument: () => void;
  portalDocument: boolean | Document;
}

const mapDispatchToProps = {
  storePortalDocument,
  removePortalDocument
};

const mapStateToProps = createSelector(
  portalDocumentSelector,
  (portalDocument: boolean | Document) => ({
    portalDocument
  })
);

const PreviewPortal = (props: PreviewPortalProps): JSX.Element => {
  let externalWindow: Window | null = null;
  const containerEl = document.createElement('div');
  const titleEl = document.createElement('title');
  const styleEl = document.createElement('style');

  useEffect(() => {
    const { t, windowTitle } = props;
    titleEl.innerText = `${t('learn.editor-tabs.preview')} | ${windowTitle}`;

    styleEl.innerHTML = `
      #fcc-main-frame {
        width: 100%;
        height: 100%;
        border: none;
      }
    `;

    externalWindow = window.open(
      '',
      '',
      'width=960,height=540,left=100,top=100'
    );

    externalWindow?.document.head.appendChild(titleEl);
    externalWindow?.document.head.appendChild(styleEl);
    externalWindow?.document.body.setAttribute(
      'style',
      `
        margin: 0px;
        padding: 0px;
        overflow: hidden;
      `
    );
    externalWindow?.document.body.appendChild(containerEl);
    externalWindow?.addEventListener('beforeunload', () => {
      props.togglePane('showPreviewPortal');
    });

    props.storePortalDocument(externalWindow?.document);
    return function cleanup() {
      externalWindow?.close();
      removePortalDocument();
    };
  }, []);
  return ReactDOM.createPortal(props.children, containerEl);
};

PreviewPortal.displayName = 'PreviewPortal';

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(withTranslation()(PreviewPortal));

additionally, in portal-preview file, what is the purpose of the mainwindow when is this case reached?

give me a little more time please. I think i know what to do. ill get back to you shortly

That does sound like a decent plan of action. The main issue is ensuring the window closes when it should. Should the external window close if I navigate to /learn?

If you do this, be sure to do it in a separate PR.

My understanding is that line of code is necessary to prevent cases where the Camper navigates away from the current page, and the external window remains open. That is, before the main Window is disposed, ensure the external Window is closed.

1 Like

the problem with that is if I dont close it on unmount an extra blank window may remain hanging around. pull the changes I made and try it:

fix close external window

comment out this line in the unmount:

    this.externalWindow?.close();

it works as desired when you advance in the curriculum. however press the open and close modal control button a few times and you will see the extra windows being created.

The only thing I can think of is to have the window close from a different component (not the preview portal)

I never saw that triggered.

update: I just moved it into desktop layout, its working as expected.

that is the last thing left to do to close the issue

edit: ok I think ive solved for it,

i removed that condition with the main window because I never saw it triggered and I dont think it was actually doing anything. I will make pull request.