coatless / quarto-webr

Community developed Quarto Extension to Embed webR for HTML Documents, RevealJS, Websites, Blogs, and Books.
https://quarto-webr.thecoatlessprofessor.com/
394 stars 19 forks source link

[Feature]: Change editor theme by css #176

Closed ute closed 7 months ago

ute commented 8 months ago

Feature Description

It might be nice for fans of the dark side to be able to choose a dark editor theme 😎

I was wondering whether this should be accomplished through yaml, or css. Half of my students want dark course notes and the other half want light course notes, therefore I make use of quarto's dark/light theme switch and let them choose themselves. Therefore I think it should be done the css way.

EDIT: That was a shortcut that is not working because the quarto switch does not trigger reload of monaco with new defaults..., sorry for that!


For the monaco editor part, it is actually easy to accomplish via a custom variable

.qwebr-editor {
  --theme: vs-dark;
}

This variable can be accessed in qwebr-monaco-editor-element.js and used directly

  editor = monaco.editor.create(editorDiv, {
    ...
     theme: getComputedStyle(editorDiv).getPropertyValue("--theme")??"vs-light",
   ...
  }

while the border colors etc would also have to be adjusted. In this context, I found that commenting out the color and background-color properties of .qwebr-output-code-stdout and .reveal pre div code.qwebr-output-code-stderr saves some work by using the default for output from knitr code cells, which is legible also in dark mode.

What do you think? do you have theming plans for the near future?

coatless commented 8 months ago

@ute dark mode is the bees knees.

I love the getComputedStyle() and getPropertyValue() usage to pull data from CSS. However, we need to assume that about 90% of the user base doesn't have a web dev background.

So, how about a document-level option similar to Quarto's theme for setting the editor's overall style? I'm not sold on the need for per-code cell theme changes.

Another question would be if a Quarto CSS variable is being set when dark mode is enabled? Maybe we can set theme to auto in that case and, then, determine the appropriate theme.

ute commented 8 months ago

I would not change the theme per code cell, either. The auto suggestion is briliant, and I initially also hoped to be able to find out how to detect if dark mode is set. I was unable to find it, and therefore came up with the css thing.

With the hack that I suggested, the editor theme does not immediately change with the quarto switch, it does so only after the page reloaded. Guess they set a cookie? I think it is fine to live with this, normally a reader of a website would not constantly switch between modes, and my students will be happy for the easter egg once they reload the page.

I don't think it is possible for revealjs to have that switch between modes, so therefore a monaco theme could be set by one css to be included or omitted, or by yaml, of course.

I have no web dev background either and am just poking around, but searched the quarto-dev/quarto-cli repository again, and got new hope: they probe document.body.classList.contains("quarto-dark")

coatless commented 8 months ago

Yup! Some other discussion by @jimjam-slam

https://github.com/quarto-dev/quarto-cli/discussions/7713#discussioncomment-7675300

I'm thinking a mutation event on the body tag would be the best bet to cause the editors on the page to update/change, c.f.

// Function to update Monaco Editors when body class changes
function updateMonacoEditorsOnBodyClassChange() {
    // Select the body element
    const body = document.querySelector('body');

    // Options for the observer (which mutations to observe)
    const observerOptions = {
        attributes: true,  // Observe changes to attributes
        attributeFilter: ['class'] // Only observe changes to the 'class' attribute
    };

    // Callback function to execute when mutations are observed
    const bodyClassChangeCallback = function(mutationsList, observer) {
        for(let mutation of mutationsList) {
            if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                // Class attribute has changed
                // Update all Monaco Editors on the page
                updateMonacoEditors();
            }
        }
    };

    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(bodyClassChangeCallback);

    // Start observing the target node for configured mutations
    observer.observe(body, observerOptions);
}

// Function to update all instances of Monaco Editors on the page
function updateMonacoEditorTheme() {
    // Find all Monaco Editor instances on the page
    const editorElements = document.querySelectorAll('.monaco-editor');

    // Determine what VS to use
    const vsThemeToUse = document.body.classList.contains("quarto-dark") ? 'vs-dark' : 'vs' ;

    // Iterate through each editor element and update it as needed
    editorElements.forEach(function(editorElement) {
        // Access the Monaco Editor instance associated with this element
        const editorInstance = editorElement && editorElement._editor;

        if (editorInstance) {
            // Update the theme of the editor when body class changes
            editorInstance.updateOptions({ theme: vsThemeToUse });
        }
    });
}

// Call the function to start observing changes to body class
updateMonacoEditorsOnBodyClassChange();

Rough code above.

ute commented 8 months ago

I've (in snail-speed) tried to probe document.body.classList within qwebrCreateMonacoEditorInstance and got a list of length 0 but which was perfectly readable in console.log. I have no clue what is going on - some kind of lazy evaluation? Leave this to the expert 😉

coatless commented 7 months ago

Okidokie, I've added the ability to switch between vs and vs-dark based on Quarto's light/dark mode toggle.

quarto-webr-light-mode-and-dark-mode

[!NOTE]

The state does persist through document refresh. So, if you select dark mode that preference will be used on subsequent pages being visited.