jupyterlite-sphinx icon indicating copy to clipboard operation
jupyterlite-sphinx copied to clipboard

React to pydata-sphinx-theme theme change

Open martinRenou opened this issue 2 years ago • 10 comments

Problem

Somewhat related to https://github.com/pydata/pydata-sphinx-theme/issues/745

pydata-sphinx-theme has a theme switch allowing to go from a light to a dark theme. We could have the embedded jupyterlite respect the current theme flavor.

In order to do this, we need to tell JupyterLab app which theme to use:

Proposed Solution

For replite

For replite it's quite straightforward, we can simply change the URL to pass the right query parameter ?theme=Jupyterlab Dark.

For JupyterLite and RetroLite

We can probably force JupyterLab to expose its app object with:

iframe.addEventListener('load', () => {
    iframe.contentWindow.document.body.dataset.exposeAppInBrowser = "true";
});

And then use the iframe.contentWindow.jupyterapp object to access the themeManager and make it switch to the theme flavor that is currently used in the docs?

Pinging @jtpio in case you have some ideas

martinRenou avatar Jul 26 '22 13:07 martinRenou

We can probably force JupyterLab to expose its app object with:

This can also be enabled with the exposeAppInBrowser setting in jupyter-lite.json: https://jupyterlite.readthedocs.io/en/latest/reference/schema-v0.html#jupyter-config-data

jtpio avatar Jul 26 '22 14:07 jtpio

Maybe https://github.com/jupyterlite/jupyterlite/pull/525 could also be relevant and apply to more apps than just repl.

jtpio avatar Jul 26 '22 14:07 jtpio

Another idea would be to have a JupyterLab extension honoring the prefers-color-scheme CSS media feature: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme

Which is what most light / dark themes use nowadays to choose a default theme on page load.

Edit: tracked in https://github.com/jupyterlab/jupyterlab/issues/8777

Although this could be done separately and would not sync the theme when a user switches from light to dark by clicking on the button.

jtpio avatar Jul 26 '22 14:07 jtpio

I wanted to work on it from our theme side and I have few comments to add here

Another idea would be to have a JupyterLab extension honoring the prefers-color-scheme CSS media feature

That won't be sufficient as theming is not natively supported by Sphinx so the few theme implementing it (pydata-sphinx-theme, sphinx-book, lutra and furo to my knowledge) are doing it in different ways and can enforce theming that is not matching with this parameter.

Also as they are all doing it a different way, I think the best way to proceed is to expose a JS handle that themes can switch when changing the theme. by doing so you will be compatible with any theme as long as their maintainers are happy to support your extention. We are willing to work on it from pydata-sphinx-theme (https://github.com/pydata/pydata-sphinx-theme/issues/745) but we are missing this interface.

12rambau avatar Oct 18 '22 10:10 12rambau

looking down the web it seems that I'm not facing any cross-domain issues when manipulating the content of the iframe via javascript. I tried the following code and it worked like a charm:

var frm = document.querySelectorAll(".jupyterlite_sphinx_iframe")

var cssLink = document.createElement("link");
cssLink.href = "style.css"; 
cssLink.rel = "stylesheet"; 
cssLink.type = "text/css";

frm.forEach( frame => {
    frame.contentWindow.document.head.appendChild(cssLink);
})

I think the next step for me is to change the css when a theme changed is triggered in the page.

3 questions:

  • Are all the css already loaded in the iframe (meaning both the dark and light theme of jupyterlab) ?
  • What is the parameter that drive the used theme in jupyterlite ?
  • Is it the same for all 3 supported interfaces (jupyterlite, retrolite and replite) ?

12rambau avatar Nov 14 '22 17:11 12rambau

Uploading Mobile​Game​ …

Aunyaphatcode avatar Jun 23 '24 20:06 Aunyaphatcode

Hi!

I ended up here on this issue while reviewing pydata/pydata-sphinx-theme#1900. I then started working on my own implementation (in pydata-sphinx-theme) to sync color mode (light/dark) between pydata-sphinx-theme and jupyterlite-sphinx. I was able to come up with a reliable, working way to switch a loaded JupyterLite iframe from one theme to another. But while working on this I realized that I also really want a way to tell a JupyterLite application to use a particular theme before it loads. That way we avoid the iframe flashing from light to dark as it loads on the page.

I want to be able to provide the theme configuration at run time with JavaScript, rather than at build time. The reason why is that a PST page doesn't know until run time whether it is in dark or light mode. This is because PST's JavaScript stores the user's preference in local storage, and then it queries local storage before the page renders to determine whether the page should be in dark or light mode.

After digging through docs and source code, I'm not sure how to load a JupyterLite application with a given theme at runtime. As mentioned in this issue's description, for REPLite I can get consistent behavior by passing theme as a query string parameter like so: ?theme=${encodeURIComponent("Theme Name")}. But this does not work for a JupyterLite iframe.

If every JupyterLite application took theme as a query string parameter and ensured that the application applies that theme before it renders, that would solve my problem.

Is there some other way to load JupyterLite at run time with a given theme?

PS. As a side note, I think I discovered a bug while working on this. If you use jupyterlite-sphinx's directives to put a REPLite and JupyterLite together on the same page, the JupyterLite instance will follow the theme set in the REPLite instance. So for example, if I click to load the JupyterLite instance before clicking a dark-mode REPLite loader, then the JupyterLite instance will load in light mode. If I click to load the dark-mode REPLite instance first, then JupyterLite will load in dark mode.

gabalafou avatar Aug 29 '24 12:08 gabalafou

Is there some other way to load JupyterLite at run time with a given theme?

Although probably less convenient that a URL parameter, there could be a bridge between the host page and the IFrame forwarding JupyterLab commands, similar to https://jupyterlite.readthedocs.io/en/stable/howto/configure/advanced/iframe.html.

Otherwise there was some experimentation at some point about being able to extension the REPL URL parameters, which could maybe be applied to other JupyterLite apps too (lab, notebook, voici?): https://github.com/jupyterlite/jupyterlite/pull/525. It's still a draft, but it could be interesting to revive the PR.

I think I discovered a bug while working on this. If you use jupyterlite-sphinx's directives to put a REPLite and JupyterLite together on the same page, the JupyterLite instance will follow the theme set in the REPLite instance. So for example, if I click to load the JupyterLite instance before clicking a dark-mode REPLite loader, then the JupyterLite instance will load in light mode. If I click to load the dark-mode REPLite instance first, then JupyterLite will load in dark mode.

This is because JupyterLite stores settings (for example the theme), in the browser storage by default. Which would then be shared when the different apps are served from the same host / port. But this can normally be disabled using a volatile storage driver like memoryStorageDriver: https://jupyterlite.readthedocs.io/en/stable/howto/configure/storage.html#settings-storage

jtpio avatar Sep 03 '24 06:09 jtpio

Thanks @jtpio for the explanation!

I was aware of the iframe bridge method to change the theme of a running Jupyter app instance. I assume that you're suggesting to use the iframe bridge not just to change the theme of a running instance but to wait for a signal from the host page to load the Jupyter app with a particular theme. I don't think I have enough know-how to revive jupyterlite/jupyterlite#525, but for jupyterlite-sphinx, I could create an extension that essentially copies functionality from the repl-extension: by parsing theme out of the iframe's URL query string and calling themeManager.setTheme(themeName).

As for storing settings, would it make sense to change jupyterlite-sphinx so that it uses memoryStorageDriver by default? That makes sense to me on the one hand because it seems that there is a valid use case to have several JupyterLite instances on the same page, or across a set of pages (but still with same host and port), with different configurations. On the other hand, it seems that there are other valid use cases for which this default would be problematic.

gabalafou avatar Sep 03 '24 10:09 gabalafou

I could create an extension that essentially copies functionality from the repl-extension: by parsing theme out of the iframe's URL query string and calling themeManager.setTheme(themeName).

A custom extension sounds good for now. Then maybe we could try to revive https://github.com/jupyterlite/jupyterlite/pull/525 to see if it can be made more generic.

As for storing settings, would it make sense to change jupyterlite-sphinx so that it uses memoryStorageDriver by default?

It might indeed make sense. If this is the case by default, we could then document how to disable it for those who really need persistence. Folks browsing documentation likely don't use the JupyterLite REPL and apps to persist content and settings.

jtpio avatar Sep 03 '24 11:09 jtpio