Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React to pydata-sphinx-theme theme change #69

Open
martinRenou opened this issue Jul 26, 2022 · 10 comments
Open

React to pydata-sphinx-theme theme change #69

martinRenou opened this issue Jul 26, 2022 · 10 comments
Labels
enhancement New feature or request

Comments

@martinRenou
Copy link
Member

Problem

Somewhat related to pydata/pydata-sphinx-theme#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 martinRenou added the enhancement New feature or request label Jul 26, 2022
@jtpio
Copy link
Member

jtpio commented Jul 26, 2022

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
Copy link
Member

jtpio commented Jul 26, 2022

Maybe jupyterlite/jupyterlite#525 could also be relevant and apply to more apps than just repl.

@jtpio
Copy link
Member

jtpio commented Jul 26, 2022

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 jupyterlab/jupyterlab#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.

@12rambau
Copy link
Contributor

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 (pydata/pydata-sphinx-theme#745) but we are missing this interface.

@12rambau
Copy link
Contributor

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) ?

@Aunyaphatcode
Copy link

@gabalafou
Copy link

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.

@jtpio
Copy link
Member

jtpio commented Sep 3, 2024

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?): jupyterlite/jupyterlite#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

@gabalafou
Copy link

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.

@jtpio
Copy link
Member

jtpio commented Sep 3, 2024

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 jupyterlite/jupyterlite#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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants