From 6d18058b3cc774a714a9d95f5acf094e70c164c3 Mon Sep 17 00:00:00 2001 From: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:05:59 +0100 Subject: [PATCH] replace webr extension; upkeep (#287) - replace [`r-wasm/quarto-live`](https://github.com/r-wasm/quarto-live) with [`coatless/webr`](https://github.com/coatless/quarto-webr) - close https://github.com/insightsengineering/tlg-catalog/issues/281 - remove one extension - add another one - slightly change css - there is a new div ID - updates webr code chunks -> `{webr-r}` into `{webr}` - update webr config options in `_quarto.yaml` - change the format from `html` into `live-html` - `live-html` webr code chunks has access to previous webr code chunks. Therefore we don't have to include "setup" chunks in the webr code chunks. That reduces the amount of code the user sees which is a nice feature. - Other changes: - fixes layout error - close https://github.com/insightsengineering/tlg-catalog/issues/285 - apply full width - apply auto-formatting from VSCode quarto extension - add `## Output` - to prepare for extending catalog with cards - so that it would be easier to add new child sections inside Outputs - remove Quarto comments (i.e. ``) as these are pushed to the output HTML document - add `warning = FALSE, message = FALSE` to the startup code chunks to hide package startup messages - change inline r code chunk (with `webr_code_labels`) to multiline code chunk so that it won't be changed by IDE autoformatters. This was already detected previously and apparently in this case the spaces matter so it would prevent auto-destroys in the future - review all existing skips - skip strict, skip tests, skip eval --------- Signed-off-by: Pawel Rucki <12943682+pawelru@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- book/_extensions/coatless/webr/_extension.yml | 8 - .../coatless/webr/qwebr-cell-elements.js | 274 ------ .../webr/qwebr-cell-initialization.js | 114 --- .../coatless/webr/qwebr-compute-engine.js | 388 -------- .../qwebr-document-engine-initialization.js | 98 -- .../coatless/webr/qwebr-document-history.js | 110 --- .../coatless/webr/qwebr-document-settings.js | 26 - .../coatless/webr/qwebr-document-status.js | 368 -------- .../webr/qwebr-monaco-editor-element.js | 228 ----- .../webr/qwebr-monaco-editor-init.html | 10 - .../coatless/webr/qwebr-styling.css | 268 ------ .../coatless/webr/qwebr-theme-switch.js | 42 - book/_extensions/coatless/webr/template.qmd | 36 - book/_extensions/coatless/webr/webr.lua | 743 --------------- book/_extensions/r-wasm/live/_extension.yml | 15 + book/_extensions/r-wasm/live/_gradethis.qmd | 40 + book/_extensions/r-wasm/live/_knitr.qmd | 32 + book/_extensions/r-wasm/live/live.lua | 733 +++++++++++++++ .../r-wasm/live/resources/live-runtime.css | 1 + .../r-wasm/live/resources/live-runtime.js | 130 +++ .../r-wasm/live/resources/pyodide-worker.js | 18 + .../r-wasm/live/resources/tinyyaml.lua | 883 ++++++++++++++++++ .../r-wasm/live/templates/interpolate.ojs | 16 + .../r-wasm/live/templates/pyodide-editor.ojs | 16 + .../live/templates/pyodide-evaluate.ojs | 41 + .../live/templates/pyodide-exercise.ojs | 30 + .../r-wasm/live/templates/pyodide-setup.ojs | 129 +++ .../r-wasm/live/templates/webr-editor.ojs | 11 + .../r-wasm/live/templates/webr-evaluate.ojs | 40 + .../r-wasm/live/templates/webr-exercise.ojs | 29 + .../r-wasm/live/templates/webr-setup.ojs | 123 +++ .../r-wasm/live/templates/webr-widget.ojs | 10 + book/_quarto.yml | 18 +- book/_utils/shinylive.qmd | 8 +- book/_utils/webr.qmd | 13 +- book/_utils/webr_no_include.qmd | 19 + book/assets/css/style.css | 2 +- book/graphs/efficacy/fstg01.qmd | 61 +- book/graphs/efficacy/fstg02.qmd | 48 +- book/graphs/efficacy/kmg01.qmd | 64 +- book/graphs/efficacy/mmrmg01.qmd | 23 +- book/graphs/efficacy/mmrmg02.qmd | 11 +- book/graphs/other/brg01.qmd | 71 +- book/graphs/other/bwg01.qmd | 67 +- book/graphs/other/cig01.qmd | 53 +- book/graphs/other/ippg01.qmd | 38 +- book/graphs/other/ltg01.qmd | 81 +- book/graphs/other/mng01.qmd | 72 +- book/graphs/pharmacokinetic/pkcg01.qmd | 32 +- book/graphs/pharmacokinetic/pkcg02.qmd | 32 +- book/graphs/pharmacokinetic/pkcg03.qmd | 32 +- book/graphs/pharmacokinetic/pkpg01.qmd | 35 +- book/graphs/pharmacokinetic/pkpg02.qmd | 32 +- book/graphs/pharmacokinetic/pkpg03.qmd | 22 +- book/graphs/pharmacokinetic/pkpg04.qmd | 22 +- book/graphs/pharmacokinetic/pkpg06.qmd | 48 +- book/listings/ADA/adal02.qmd | 19 +- book/listings/ECG/egl01.qmd | 19 +- book/listings/adverse-events/ael01.qmd | 19 +- book/listings/adverse-events/ael01_nollt.qmd | 19 +- book/listings/adverse-events/ael02.qmd | 19 +- book/listings/adverse-events/ael02_ed.qmd | 19 +- book/listings/adverse-events/ael03.qmd | 19 +- book/listings/adverse-events/ael04.qmd | 19 +- .../concomitant-medications/cml01.qmd | 19 +- .../concomitant-medications/cml02a_gl.qmd | 19 +- .../concomitant-medications/cml02b_gl.qmd | 19 +- .../dsur4.qmd | 19 +- book/listings/disposition/dsl01.qmd | 19 +- book/listings/disposition/dsl02.qmd | 19 +- book/listings/efficacy/oncl01.qmd | 19 +- book/listings/exposure/exl01.qmd | 19 +- book/listings/lab-results/lbl01.qmd | 19 +- book/listings/lab-results/lbl01_rls.qmd | 19 +- book/listings/lab-results/lbl02a.qmd | 19 +- book/listings/lab-results/lbl02a_rls.qmd | 19 +- book/listings/lab-results/lbl02b.qmd | 19 +- book/listings/medical-history/mhl01.qmd | 19 +- book/listings/pharmacokinetic/adal01.qmd | 16 +- book/listings/pharmacokinetic/pkcl01.qmd | 19 +- book/listings/pharmacokinetic/pkcl02.qmd | 19 +- book/listings/pharmacokinetic/pkpl01.qmd | 19 +- book/listings/pharmacokinetic/pkpl02.qmd | 19 +- book/listings/pharmacokinetic/pkpl04.qmd | 19 +- book/listings/vital-signs/vsl01.qmd | 19 +- book/tables/ADA/adat01.qmd | 22 +- book/tables/ADA/adat02.qmd | 19 +- book/tables/ADA/adat03.qmd | 19 +- book/tables/ADA/adat04a.qmd | 23 +- book/tables/ADA/adat04b.qmd | 23 +- book/tables/ECG/egt01.qmd | 23 +- book/tables/ECG/egt02.qmd | 26 +- book/tables/ECG/egt03.qmd | 32 +- book/tables/ECG/egt04.qmd | 25 +- book/tables/ECG/egt05_qtcat.qmd | 19 +- book/tables/adverse-events/aet01.qmd | 46 +- book/tables/adverse-events/aet01_aesi.qmd | 39 +- book/tables/adverse-events/aet02.qmd | 85 +- book/tables/adverse-events/aet02_smq.qmd | 26 +- book/tables/adverse-events/aet03.qmd | 25 +- book/tables/adverse-events/aet04.qmd | 59 +- book/tables/adverse-events/aet04_pi.qmd | 83 +- book/tables/adverse-events/aet05.qmd | 26 +- book/tables/adverse-events/aet05_all.qmd | 26 +- book/tables/adverse-events/aet06.qmd | 41 +- book/tables/adverse-events/aet06_smq.qmd | 31 +- book/tables/adverse-events/aet07.qmd | 26 +- book/tables/adverse-events/aet09.qmd | 26 +- book/tables/adverse-events/aet09_smq.qmd | 30 +- book/tables/adverse-events/aet10.qmd | 29 +- book/tables/concomitant-medications/cmt01.qmd | 36 +- .../tables/concomitant-medications/cmt01a.qmd | 36 +- .../tables/concomitant-medications/cmt01b.qmd | 31 +- .../concomitant-medications/cmt02_pt.qmd | 19 +- book/tables/deaths/dtht01.qmd | 36 +- book/tables/demography/dmt01.qmd | 41 +- book/tables/disclosures/disclosurest01.qmd | 71 +- book/tables/disclosures/eudrat01.qmd | 19 +- book/tables/disclosures/eudrat02.qmd | 19 +- book/tables/disposition/dst01.qmd | 31 +- book/tables/disposition/pdt01.qmd | 19 +- book/tables/disposition/pdt02.qmd | 19 +- book/tables/efficacy/aovt01.qmd | 19 +- book/tables/efficacy/aovt02.qmd | 24 +- book/tables/efficacy/aovt03.qmd | 23 +- book/tables/efficacy/cfbt01.qmd | 19 +- book/tables/efficacy/cmht01.qmd | 33 +- book/tables/efficacy/coxt01.qmd | 56 +- book/tables/efficacy/coxt02.qmd | 48 +- book/tables/efficacy/dort01.qmd | 36 +- book/tables/efficacy/lgrt02.qmd | 36 +- book/tables/efficacy/mmrmt01.qmd | 27 +- book/tables/efficacy/onct05.qmd | 51 +- book/tables/efficacy/ratet01.qmd | 28 +- book/tables/efficacy/rbmit01.qmd | 43 +- book/tables/efficacy/rspt01.qmd | 62 +- book/tables/efficacy/ttet01.qmd | 46 +- book/tables/exposure/ext01.qmd | 31 +- book/tables/lab-results/lbt01.qmd | 23 +- book/tables/lab-results/lbt02.qmd | 19 +- book/tables/lab-results/lbt03.qmd | 29 +- book/tables/lab-results/lbt04.qmd | 19 +- book/tables/lab-results/lbt05.qmd | 31 +- book/tables/lab-results/lbt06.qmd | 26 +- book/tables/lab-results/lbt07.qmd | 19 +- book/tables/lab-results/lbt08.qmd | 24 +- book/tables/lab-results/lbt09.qmd | 26 +- book/tables/lab-results/lbt10.qmd | 26 +- book/tables/lab-results/lbt10_bl.qmd | 26 +- book/tables/lab-results/lbt11.qmd | 31 +- book/tables/lab-results/lbt11_bl.qmd | 31 +- book/tables/lab-results/lbt12.qmd | 19 +- book/tables/lab-results/lbt12_bl.qmd | 19 +- book/tables/lab-results/lbt13.qmd | 52 +- book/tables/lab-results/lbt14.qmd | 52 +- book/tables/lab-results/lbt15.qmd | 26 +- book/tables/medical-history/mht01.qmd | 39 +- book/tables/pharmacokinetic/pkct01.qmd | 31 +- book/tables/pharmacokinetic/pkpt02.qmd | 32 +- book/tables/pharmacokinetic/pkpt03.qmd | 32 +- book/tables/pharmacokinetic/pkpt04.qmd | 32 +- book/tables/pharmacokinetic/pkpt05.qmd | 32 +- book/tables/pharmacokinetic/pkpt06.qmd | 32 +- book/tables/pharmacokinetic/pkpt07.qmd | 32 +- book/tables/pharmacokinetic/pkpt08.qmd | 22 +- book/tables/pharmacokinetic/pkpt11.qmd | 20 +- book/tables/risk-management-plan/rmpt01.qmd | 19 +- book/tables/risk-management-plan/rmpt03.qmd | 26 +- book/tables/risk-management-plan/rmpt04.qmd | 19 +- book/tables/risk-management-plan/rmpt05.qmd | 19 +- book/tables/risk-management-plan/rmpt06.qmd | 42 +- book/tables/safety/enrollment01.qmd | 43 +- book/tables/vital-signs/vst01.qmd | 23 +- book/tables/vital-signs/vst02.qmd | 26 +- 174 files changed, 5218 insertions(+), 4051 deletions(-) delete mode 100644 book/_extensions/coatless/webr/_extension.yml delete mode 100644 book/_extensions/coatless/webr/qwebr-cell-elements.js delete mode 100644 book/_extensions/coatless/webr/qwebr-cell-initialization.js delete mode 100644 book/_extensions/coatless/webr/qwebr-compute-engine.js delete mode 100644 book/_extensions/coatless/webr/qwebr-document-engine-initialization.js delete mode 100644 book/_extensions/coatless/webr/qwebr-document-history.js delete mode 100644 book/_extensions/coatless/webr/qwebr-document-settings.js delete mode 100644 book/_extensions/coatless/webr/qwebr-document-status.js delete mode 100644 book/_extensions/coatless/webr/qwebr-monaco-editor-element.js delete mode 100644 book/_extensions/coatless/webr/qwebr-monaco-editor-init.html delete mode 100644 book/_extensions/coatless/webr/qwebr-styling.css delete mode 100644 book/_extensions/coatless/webr/qwebr-theme-switch.js delete mode 100644 book/_extensions/coatless/webr/template.qmd delete mode 100644 book/_extensions/coatless/webr/webr.lua create mode 100644 book/_extensions/r-wasm/live/_extension.yml create mode 100644 book/_extensions/r-wasm/live/_gradethis.qmd create mode 100644 book/_extensions/r-wasm/live/_knitr.qmd create mode 100644 book/_extensions/r-wasm/live/live.lua create mode 100644 book/_extensions/r-wasm/live/resources/live-runtime.css create mode 100644 book/_extensions/r-wasm/live/resources/live-runtime.js create mode 100644 book/_extensions/r-wasm/live/resources/pyodide-worker.js create mode 100644 book/_extensions/r-wasm/live/resources/tinyyaml.lua create mode 100644 book/_extensions/r-wasm/live/templates/interpolate.ojs create mode 100644 book/_extensions/r-wasm/live/templates/pyodide-editor.ojs create mode 100644 book/_extensions/r-wasm/live/templates/pyodide-evaluate.ojs create mode 100644 book/_extensions/r-wasm/live/templates/pyodide-exercise.ojs create mode 100644 book/_extensions/r-wasm/live/templates/pyodide-setup.ojs create mode 100644 book/_extensions/r-wasm/live/templates/webr-editor.ojs create mode 100644 book/_extensions/r-wasm/live/templates/webr-evaluate.ojs create mode 100644 book/_extensions/r-wasm/live/templates/webr-exercise.ojs create mode 100644 book/_extensions/r-wasm/live/templates/webr-setup.ojs create mode 100644 book/_extensions/r-wasm/live/templates/webr-widget.ojs create mode 100644 book/_utils/webr_no_include.qmd diff --git a/book/_extensions/coatless/webr/_extension.yml b/book/_extensions/coatless/webr/_extension.yml deleted file mode 100644 index 206c449f25..0000000000 --- a/book/_extensions/coatless/webr/_extension.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: webr -title: Embedded webr code cells -author: James Joseph Balamuta -version: 0.4.3-dev.2 -quarto-required: ">=1.4.554" -contributes: - filters: - - webr.lua diff --git a/book/_extensions/coatless/webr/qwebr-cell-elements.js b/book/_extensions/coatless/webr/qwebr-cell-elements.js deleted file mode 100644 index 5037bdc350..0000000000 --- a/book/_extensions/coatless/webr/qwebr-cell-elements.js +++ /dev/null @@ -1,274 +0,0 @@ -// Supported Evaluation Types for Context -globalThis.EvalTypes = Object.freeze({ - Interactive: 'interactive', - Setup: 'setup', - Output: 'output', -}); - -// Function that obtains the font size for a given element -globalThis.qwebrCurrentFontSizeOnElement = function(element, cssProperty = 'font-size') { - - const currentFontSize = parseFloat( - window - .getComputedStyle(element) - .getPropertyValue(cssProperty) - ); - - return currentFontSize; -} - -// Function to determine font scaling -globalThis.qwebrScaledFontSize = function(div, qwebrOptions) { - // Determine if we should compute font-size using RevealJS's `--r-main-font-size` - // or if we can directly use the document's `font-size`. - const cssProperty = document.body.classList.contains('reveal') ? - "--r-main-font-size" : "font-size"; - - // Get the current font size on the div element - const elementFontSize = qwebrCurrentFontSizeOnElement(div, cssProperty); - - // Determine the scaled font size value - const scaledFontSize = ((qwebrOptions['editor-font-scale'] ?? 1) * elementFontSize) ?? 17.5; - - return scaledFontSize; -} - - -// Function that dispatches the creation request -globalThis.qwebrCreateHTMLElement = function ( - cellData -) { - - // Extract key components - const evalType = cellData.options.context; - const qwebrCounter = cellData.id; - - // We make an assumption that insertion points are defined by the Lua filter as: - // qwebr-insertion-location-{qwebrCounter} - const elementLocator = document.getElementById(`qwebr-insertion-location-${qwebrCounter}`); - - // Figure out the routine to use to insert the element. - let qwebrElement; - switch ( evalType ) { - case EvalTypes.Interactive: - qwebrElement = qwebrCreateInteractiveElement(qwebrCounter, cellData.options); - break; - case EvalTypes.Output: - qwebrElement = qwebrCreateNonInteractiveOutputElement(qwebrCounter, cellData.options); - break; - case EvalTypes.Setup: - qwebrElement = qwebrCreateNonInteractiveSetupElement(qwebrCounter, cellData.options); - break; - default: - qwebrElement = document.createElement('div'); - qwebrElement.textContent = 'Error creating `quarto-webr` element'; - } - - // Insert the dynamically generated object at the document location. - elementLocator.appendChild(qwebrElement); -}; - -// Function that setups the interactive element creation -globalThis.qwebrCreateInteractiveElement = function (qwebrCounter, qwebrOptions) { - - // Create main div element - var mainDiv = document.createElement('div'); - mainDiv.id = 'qwebr-interactive-area-' + qwebrCounter; - mainDiv.className = `qwebr-interactive-area`; - if (qwebrOptions.classes) { - mainDiv.className += " " + qwebrOptions.classes - } - - // Add a unique cell identifier that users can customize - if (qwebrOptions.label) { - mainDiv.setAttribute('data-id', qwebrOptions.label); - } - - // Create toolbar div - var toolbarDiv = document.createElement('div'); - toolbarDiv.className = 'qwebr-editor-toolbar'; - toolbarDiv.id = 'qwebr-editor-toolbar-' + qwebrCounter; - - // Create a div to hold the left buttons - var leftButtonsDiv = document.createElement('div'); - leftButtonsDiv.className = 'qwebr-editor-toolbar-left-buttons'; - - // Create a div to hold the right buttons - var rightButtonsDiv = document.createElement('div'); - rightButtonsDiv.className = 'qwebr-editor-toolbar-right-buttons'; - - // Create Run Code button - var runCodeButton = document.createElement('button'); - runCodeButton.className = 'btn btn-default qwebr-button qwebr-button-run'; - runCodeButton.disabled = true; - runCodeButton.type = 'button'; - runCodeButton.id = 'qwebr-button-run-' + qwebrCounter; - runCodeButton.textContent = '🟡 Loading webR...'; - runCodeButton.title = `Run code (Shift + Enter)`; - - // Append buttons to the leftButtonsDiv - leftButtonsDiv.appendChild(runCodeButton); - - // Create Reset button - var resetButton = document.createElement('button'); - resetButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-reset'; - resetButton.type = 'button'; - resetButton.id = 'qwebr-button-reset-' + qwebrCounter; - resetButton.title = 'Start over'; - resetButton.innerHTML = ''; - - // Create Copy button - var copyButton = document.createElement('button'); - copyButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-copy'; - copyButton.type = 'button'; - copyButton.id = 'qwebr-button-copy-' + qwebrCounter; - copyButton.title = 'Copy code'; - copyButton.innerHTML = ''; - - // Append buttons to the rightButtonsDiv - rightButtonsDiv.appendChild(resetButton); - rightButtonsDiv.appendChild(copyButton); - - // Create console area div - var consoleAreaDiv = document.createElement('div'); - consoleAreaDiv.id = 'qwebr-console-area-' + qwebrCounter; - consoleAreaDiv.className = 'qwebr-console-area'; - - // Create editor div - var editorDiv = document.createElement('div'); - editorDiv.id = 'qwebr-editor-' + qwebrCounter; - editorDiv.className = 'qwebr-editor'; - - // Create output code area div - var outputCodeAreaDiv = document.createElement('div'); - outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; - outputCodeAreaDiv.className = 'qwebr-output-code-area'; - outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); - - // Create pre element inside output code area - var preElement = document.createElement('pre'); - preElement.style.visibility = 'hidden'; - outputCodeAreaDiv.appendChild(preElement); - - // Create output graph area div - var outputGraphAreaDiv = document.createElement('div'); - outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; - outputGraphAreaDiv.className = 'qwebr-output-graph-area'; - - // Append buttons to the toolbar - toolbarDiv.appendChild(leftButtonsDiv); - toolbarDiv.appendChild(rightButtonsDiv); - - // Append all elements to the main div - mainDiv.appendChild(toolbarDiv); - consoleAreaDiv.appendChild(editorDiv); - consoleAreaDiv.appendChild(outputCodeAreaDiv); - mainDiv.appendChild(consoleAreaDiv); - mainDiv.appendChild(outputGraphAreaDiv); - - return mainDiv; -} - -// Function that adds output structure for non-interactive output -globalThis.qwebrCreateNonInteractiveOutputElement = function(qwebrCounter, qwebrOptions) { - // Create main div element - var mainDiv = document.createElement('div'); - mainDiv.id = 'qwebr-noninteractive-area-' + qwebrCounter; - mainDiv.className = `qwebr-noninteractive-area`; - if (qwebrOptions.classes) { - mainDiv.className += " " + qwebrOptions.classes - } - - // Add a unique cell identifier that users can customize - if (qwebrOptions.label) { - mainDiv.setAttribute('data-id', qwebrOptions.label); - } - - // Create a status container div - var statusContainer = createLoadingContainer(qwebrCounter); - - // Create output code area div - var outputCodeAreaDiv = document.createElement('div'); - outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; - outputCodeAreaDiv.className = 'qwebr-output-code-area'; - outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); - - // Create pre element inside output code area - var preElement = document.createElement('pre'); - preElement.style.visibility = 'hidden'; - outputCodeAreaDiv.appendChild(preElement); - - // Create output graph area div - var outputGraphAreaDiv = document.createElement('div'); - outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; - outputGraphAreaDiv.className = 'qwebr-output-graph-area'; - - // Append all elements to the main div - mainDiv.appendChild(statusContainer); - mainDiv.appendChild(outputCodeAreaDiv); - mainDiv.appendChild(outputGraphAreaDiv); - - return mainDiv; -}; - -// Function that adds a stub in the page to indicate a setup cell was used. -globalThis.qwebrCreateNonInteractiveSetupElement = function(qwebrCounter, qwebrOptions) { - // Create main div element - var mainDiv = document.createElement('div'); - mainDiv.id = `qwebr-noninteractive-setup-area-${qwebrCounter}`; - mainDiv.className = `qwebr-noninteractive-setup-area`; - if (qwebrOptions.classes) { - mainDiv.className += " " + qwebrOptions.classes - } - - - // Add a unique cell identifier that users can customize - if (qwebrOptions.label) { - mainDiv.setAttribute('data-id', qwebrOptions.label); - } - - // Create a status container div - var statusContainer = createLoadingContainer(qwebrCounter); - - // Append status onto the main div - mainDiv.appendChild(statusContainer); - - return mainDiv; -} - - -// Function to create loading container with specified ID -globalThis.createLoadingContainer = function(qwebrCounter) { - - // Create a status container - const container = document.createElement('div'); - container.id = `qwebr-non-interactive-loading-container-${qwebrCounter}`; - container.className = 'qwebr-non-interactive-loading-container qwebr-cell-needs-evaluation'; - - // Create an R project logo to indicate its a code space - const rProjectIcon = document.createElement('i'); - rProjectIcon.className = 'fa-brands fa-r-project fa-3x qwebr-r-project-logo'; - - // Setup a loading icon from font awesome - const spinnerIcon = document.createElement('i'); - spinnerIcon.className = 'fa-solid fa-spinner fa-spin fa-1x qwebr-icon-status-spinner'; - - // Add a section for status text - const statusText = document.createElement('p'); - statusText.id = `qwebr-status-text-${qwebrCounter}`; - statusText.className = `qwebr-status-text qwebr-cell-needs-evaluation`; - statusText.innerText = 'Loading webR...'; - - // Incorporate an inner container - const innerContainer = document.createElement('div'); - - // Append elements to the inner container - innerContainer.appendChild(spinnerIcon); - innerContainer.appendChild(statusText); - - // Append elements to the main container - container.appendChild(rProjectIcon); - container.appendChild(innerContainer); - - return container; -} \ No newline at end of file diff --git a/book/_extensions/coatless/webr/qwebr-cell-initialization.js b/book/_extensions/coatless/webr/qwebr-cell-initialization.js deleted file mode 100644 index 548172aeae..0000000000 --- a/book/_extensions/coatless/webr/qwebr-cell-initialization.js +++ /dev/null @@ -1,114 +0,0 @@ -// Handle cell initialization initialization -qwebrCellDetails.map( - (entry) => { - // Handle the creation of the element - qwebrCreateHTMLElement(entry); - // In the event of interactive, initialize the monaco editor - if (entry.options.context == EvalTypes.Interactive) { - qwebrCreateMonacoEditorInstance(entry); - } - } -); - -// Identify non-interactive cells (in order) -const filteredEntries = qwebrCellDetails.filter(entry => { - const contextOption = entry.options && entry.options.context; - return ['output', 'setup'].includes(contextOption) || (contextOption == "interactive" && entry.options && entry.options.autorun === 'true'); -}); - -// Condition non-interactive cells to only be run after webR finishes its initialization. -qwebrInstance.then( - async () => { - const nHiddenCells = filteredEntries.length; - var currentHiddenCell = 0; - - - // Modify button state - qwebrSetInteractiveButtonState(`🟡 Running hidden code cells ...`, false); - - // Begin processing non-interactive sections - // Due to the iteration policy, we must use a for() loop. - // Otherwise, we would need to switch to using reduce with an empty - // starting promise - for (const entry of filteredEntries) { - - // Determine cell being examined - currentHiddenCell = currentHiddenCell + 1; - const formattedMessage = `Evaluating hidden cell ${currentHiddenCell} out of ${nHiddenCells}`; - - // Update the document status header - if (qwebrShowStartupMessage) { - qwebrUpdateStatusHeader(formattedMessage); - } - - // Display the update in non-active areas - qwebrUpdateStatusMessage(formattedMessage); - - // Extract details on the active cell - const evalType = entry.options.context; - const cellCode = entry.code; - const qwebrCounter = entry.id; - - if (['output', 'setup'].includes(evalType)) { - // Disable further global status updates - const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); - activeContainer.classList.remove('qwebr-cell-needs-evaluation'); - activeContainer.classList.add('qwebr-cell-evaluated'); - - // Update status on the code cell - const activeStatus = document.getElementById(`qwebr-status-text-${qwebrCounter}`); - activeStatus.innerText = " Evaluating hidden code cell..."; - activeStatus.classList.remove('qwebr-cell-needs-evaluation'); - activeStatus.classList.add('qwebr-cell-evaluated'); - } - - switch (evalType) { - case 'interactive': - // TODO: Make this more standardized. - // At the moment, we're overriding the interactive status update by pretending its - // output-like. - const tempOptions = entry.options; - tempOptions["context"] = "output" - // Run the code in a non-interactive state that is geared to displaying output - await qwebrExecuteCode(`${cellCode}`, qwebrCounter, tempOptions); - break; - case 'output': - // Run the code in a non-interactive state that is geared to displaying output - await qwebrExecuteCode(`${cellCode}`, qwebrCounter, entry.options); - break; - case 'setup': - const activeDiv = document.getElementById(`qwebr-noninteractive-setup-area-${qwebrCounter}`); - - // Store code in history - qwebrLogCodeToHistory(cellCode, entry.options); - - // Run the code in a non-interactive state with all output thrown away - await mainWebR.evalRVoid(`${cellCode}`); - break; - default: - break; - } - - if (['output', 'setup'].includes(evalType)) { - // Disable further global status updates - const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); - // Disable visibility - activeContainer.style.visibility = 'hidden'; - activeContainer.style.display = 'none'; - } - } - } -).then( - () => { - // Release document status as ready - - if (qwebrShowStartupMessage) { - qwebrStartupMessage.innerText = "🟢 Ready!" - } - - qwebrSetInteractiveButtonState( - ` Run Code`, - true - ); - } -); \ No newline at end of file diff --git a/book/_extensions/coatless/webr/qwebr-compute-engine.js b/book/_extensions/coatless/webr/qwebr-compute-engine.js deleted file mode 100644 index a35ea11a38..0000000000 --- a/book/_extensions/coatless/webr/qwebr-compute-engine.js +++ /dev/null @@ -1,388 +0,0 @@ -// Function to verify a given JavaScript Object is empty -globalThis.qwebrIsObjectEmpty = function (arr) { - return Object.keys(arr).length === 0; -} - -// Global version of the Escape HTML function that converts HTML -// characters to their HTML entities. -globalThis.qwebrEscapeHTMLCharacters = function(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -}; - -// Passthrough results -globalThis.qwebrIdentity = function(x) { - return x; -}; - -// Append a comment -globalThis.qwebrPrefixComment = function(x, comment) { - return `${comment}${x}`; -}; - -// Function to store the code in the history -globalThis.qwebrLogCodeToHistory = function(codeToRun, options) { - qwebrRCommandHistory.push( - `# Ran code in ${options.label} at ${new Date().toLocaleString()} ----\n${codeToRun}` - ); -}; - -// Function to attach a download button onto the canvas -// allowing the user to download the image. -function qwebrImageCanvasDownloadButton(canvas, canvasContainer) { - - // Create the download button - const downloadButton = document.createElement('button'); - downloadButton.className = 'qwebr-canvas-image-download-btn'; - downloadButton.textContent = 'Download Image'; - canvasContainer.appendChild(downloadButton); - - // Trigger a download of the image when the button is clicked - downloadButton.addEventListener('click', function() { - const image = canvas.toDataURL('image/png'); - const link = document.createElement('a'); - link.href = image; - link.download = 'qwebr-canvas-image.png'; - link.click(); - }); -} - - -// Function to parse the pager results -globalThis.qwebrParseTypePager = async function (msg) { - - // Split out the event data - const { path, title, deleteFile } = msg.data; - - // Process the pager data by reading the information from disk - const paged_data = await mainWebR.FS.readFile(path).then((data) => { - // Obtain the file content - let content = new TextDecoder().decode(data); - - // Remove excessive backspace characters until none remain - while(content.match(/.[\b]/)){ - content = content.replace(/.[\b]/g, ''); - } - - // Returned cleaned data - return content; - }); - - // Unlink file if needed - if (deleteFile) { - await mainWebR.FS.unlink(path); - } - - // Return extracted data with spaces - return paged_data; -}; - - -// Function to parse the browse results -globalThis.qwebrParseTypeBrowse = async function (msg) { - - // msg.type === "browse" - const path = msg.data.url; - - // Process the browse data by reading the information from disk - const browse_data = await mainWebR.FS.readFile(path).then((data) => { - // Obtain the file content - let content = new TextDecoder().decode(data); - - return content; - }); - - // Return extracted data as-is - return browse_data; -}; - -// Function to run the code using webR and parse the output -globalThis.qwebrComputeEngine = async function( - codeToRun, - elements, - options) { - - // Call into the R compute engine that persists within the document scope. - // To be prepared for all scenarios, the following happens: - // 1. We setup a canvas device to write to by making a namespace call into the {webr} package - // 2. We use values inside of the options array to set the figure size. - // 3. We capture the output stream information (STDOUT and STERR) - // 4. We disable the current device's image creation. - // 5. Piece-wise parse the results into the different output areas - - // Create a pager variable for help/file contents - let pager = []; - - // Handle how output is processed - let showMarkup = options.results === "markup" && options.output !== "asis"; - let processOutput; - - if (showMarkup) { - processOutput = qwebrEscapeHTMLCharacters; - } else { - processOutput = qwebrIdentity; - } - - // ---- - // Convert from Inches to Pixels by using DPI (dots per inch) - // for bitmap devices (dpi * inches = pixels) - let fig_width = options["fig-width"] * options["dpi"]; - let fig_height = options["fig-height"] * options["dpi"]; - - // Initialize webR - await mainWebR.init(); - - // Configure capture output - let captureOutputOptions = { - withAutoprint: true, - captureStreams: true, - captureConditions: false, - // env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0 - }; - - // Determine if the browser supports OffScreen - if (qwebrOffScreenCanvasSupport()) { - // Mirror default options of webr::canvas() - // with changes to figure height and width. - captureOutputOptions.captureGraphics = { - width: fig_width, - height: fig_height, - bg: "white", // default: transparent - pointsize: 12, - capture: true - }; - } else { - // Disable generating graphics - captureOutputOptions.captureGraphics = false; - } - - // Store the code to run in history - qwebrLogCodeToHistory(codeToRun, options); - - // Setup a webR canvas by making a namespace call into the {webr} package - // Evaluate the R code - // Remove the active canvas silently - const result = await mainWebRCodeShelter.captureR( - `${codeToRun}`, - captureOutputOptions - ); - - // ----- - - // Start attempting to parse the result data - processResultOutput:try { - - // Avoid running through output processing - if (options.results === "hide" || options.output === "false") { - break processResultOutput; - } - - // Merge output streams of STDOUT and STDErr (messages and errors are combined.) - // Require both `warning` and `message` to be true to display `STDErr`. - const out = result.output - .filter( - evt => evt.type === "stdout" || - ( evt.type === "stderr" && (options.warning === "true" && options.message === "true")) - ) - .map((evt, index) => { - const className = `qwebr-output-code-${evt.type}`; - const outputResult = qwebrPrefixComment(processOutput(evt.data), options.comment); - return `${outputResult}`; - }) - .join("\n"); - - - // Clean the state - // We're now able to process pager events. - // As a result, we cannot maintain a true 1-to-1 output order - // without individually feeding each line - const msgs = await mainWebR.flush(); - - // Use `map` to process the filtered "pager" events asynchronously - const pager = []; - const browse = []; - - await Promise.all( - msgs.map( - async (msg) => { - - const msgType = msg.type || "unknown"; - - switch(msgType) { - case 'pager': - const pager_data = await qwebrParseTypePager(msg); - pager.push(pager_data); - break; - case 'browse': - const browse_data = await qwebrParseTypeBrowse(msg); - browse.push(browse_data); - break; - } - return; - } - ) - ); - - // Nullify the output area of content - elements.outputCodeDiv.innerHTML = ""; - elements.outputGraphDiv.innerHTML = ""; - - // Design an output object for messages - const pre = document.createElement("pre"); - if (/\S/.test(out)) { - // Display results as HTML elements to retain output styling - const div = document.createElement("div"); - div.innerHTML = out; - - // Calculate a scaled font-size value - const scaledFontSize = qwebrScaledFontSize( - elements.outputCodeDiv, options); - - // Override output code cell size - pre.style.fontSize = `${scaledFontSize}px`; - pre.appendChild(div); - } else { - // If nothing is present, hide the element. - pre.style.visibility = "hidden"; - } - - elements.outputCodeDiv.appendChild(pre); - - // Determine if we have graphs to display - if (result.images.length > 0) { - - // Create figure element - const figureElement = document.createElement("figure"); - figureElement.className = "qwebr-canvas-image"; - - // Place each rendered graphic onto a canvas element - result.images.forEach((img) => { - - // Construct canvas for object - const canvas = document.createElement("canvas"); - - // Add an image download button - qwebrImageCanvasDownloadButton(canvas, figureElement); - - // Set canvas size to image - canvas.width = img.width; - canvas.height = img.height; - - // Apply output truncations - canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`; - if (options["out-height"]) { - canvas.style.height = options["out-height"]; - } - - // Apply styling - canvas.style.display = "block"; - canvas.style.margin = "auto"; - - // Draw image onto Canvas - const ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0, img.width, img.height); - - // Append canvas to figure output area - figureElement.appendChild(canvas); - - }); - - if (options['fig-cap']) { - // Create figcaption element - const figcaptionElement = document.createElement('figcaption'); - figcaptionElement.innerText = options['fig-cap']; - // Append figcaption to figure - figureElement.appendChild(figcaptionElement); - } - - elements.outputGraphDiv.appendChild(figureElement); - - } - - // Display the pager data - if (pager.length > 0) { - // Use the `pre` element to preserve whitespace. - pager.forEach((paged_data, index) => { - const pre_pager = document.createElement("pre"); - pre_pager.innerText = paged_data; - pre_pager.classList.add("qwebr-output-code-pager"); - pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`); - elements.outputCodeDiv.appendChild(pre_pager); - }); - } - - // Display the browse data - if (browse.length > 0) { - // Use the `pre` element to preserve whitespace. - browse.forEach((browse_data, index) => { - const iframe_browse = document.createElement('iframe'); - iframe_browse.classList.add("qwebr-output-code-browse"); - iframe_browse.setAttribute("id", `qwebr-output-code-browse-editor-${elements.id}-result-${index + 1}`); - iframe_browse.style.width = "100%"; - iframe_browse.style.minHeight = "500px"; - elements.outputCodeDiv.appendChild(iframe_browse); - - iframe_browse.contentWindow.document.open(); - iframe_browse.contentWindow.document.write(browse_data); - iframe_browse.contentWindow.document.close(); - }); - } - } finally { - // Clean up the remaining code - mainWebRCodeShelter.purge(); - } -}; - -// Function to execute the code (accepts code as an argument) -globalThis.qwebrExecuteCode = async function ( - codeToRun, - id, - options = {}) { - - // If options are not passed, we fall back on the bare minimum to handle the computation - if (qwebrIsObjectEmpty(options)) { - options = { - "context": "interactive", - "fig-width": 7, "fig-height": 5, - "out-width": "700px", "out-height": "", - "dpi": 72, - "results": "markup", - "warning": "true", "message": "true", - }; - } - - // Next, we access the compute areas values - const elements = { - runButton: document.getElementById(`qwebr-button-run-${id}`), - outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`), - outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`), - id: id, - } - - // Disallowing execution of other code cells - document.querySelectorAll(".qwebr-button-run").forEach((btn) => { - btn.disabled = true; - }); - - if (options.context == EvalTypes.Interactive) { - // Emphasize the active code cell - elements.runButton.innerHTML = ' Run Code'; - } - - // Evaluate the code and parse the output into the document - await qwebrComputeEngine(codeToRun, elements, options); - - // Switch to allowing execution of code - document.querySelectorAll(".qwebr-button-run").forEach((btn) => { - btn.disabled = false; - }); - - if (options.context == EvalTypes.Interactive) { - // Revert to the initial code cell state - elements.runButton.innerHTML = ' Run Code'; - } -} diff --git a/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js b/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js deleted file mode 100644 index 723220acc0..0000000000 --- a/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js +++ /dev/null @@ -1,98 +0,0 @@ -// Function to install a single package -async function qwebrInstallRPackage(packageName) { - await mainWebR.evalRVoid(`webr::install('${packageName}');`); -} - -// Function to load a single package -async function qwebrLoadRPackage(packageName) { - await mainWebR.evalRVoid(`require('${packageName}', quietly = TRUE)`); -} - -// Generic function to process R packages -async function qwebrProcessRPackagesWithStatus(packages, processType, displayStatusMessageUpdate = true) { - // Switch between contexts - const messagePrefix = processType === 'install' ? 'Installing' : 'Loading'; - - // Modify button state - qwebrSetInteractiveButtonState(`🟡 ${messagePrefix} package ...`, false); - - // Iterate over packages - for (let i = 0; i < packages.length; i++) { - const activePackage = packages[i]; - const formattedMessage = `${messagePrefix} package ${i + 1} out of ${packages.length}: ${activePackage}`; - - // Display the update in header - if (displayStatusMessageUpdate) { - qwebrUpdateStatusHeader(formattedMessage); - } - - // Display the update in non-active areas - qwebrUpdateStatusMessage(formattedMessage); - - // Run package installation - if (processType === 'install') { - await qwebrInstallRPackage(activePackage); - } else { - await qwebrLoadRPackage(activePackage); - } - } - - // Clean slate - if (processType === 'load') { - await mainWebR.flush(); - } -} - -// Start a timer -const initializeWebRTimerStart = performance.now(); - -// Encase with a dynamic import statement -globalThis.qwebrInstance = import(qwebrCustomizedWebROptions.baseURL + "webr.mjs").then( - async ({ WebR, ChannelType }) => { - // Populate WebR options with defaults or new values based on `webr` meta - globalThis.mainWebR = new WebR(qwebrCustomizedWebROptions); - - // Initialization WebR - await mainWebR.init(); - - // Setup a shelter - globalThis.mainWebRCodeShelter = await new mainWebR.Shelter(); - - // Setup a pager to allow processing help documentation - await mainWebR.evalRVoid('webr::pager_install()'); - - // Setup a viewer to allow processing htmlwidgets. - // This might not be available in old webr version - await mainWebR.evalRVoid('try({ webr::viewer_install() })'); - - // Override the existing install.packages() to use webr::install() - await mainWebR.evalRVoid('webr::shim_install()'); - - // Specify the repositories to pull from - // Note: webR does not use the `repos` option, but instead uses `webr_pkg_repos` - // inside of `install()`. However, other R functions still pull from `repos`. - await mainWebR.evalRVoid(` - options( - webr_pkg_repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}), - repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}) - ) - `); - - // Check to see if any packages need to be installed - if (qwebrSetupRPackages) { - // Obtain only a unique list of packages - const uniqueRPackageList = Array.from(new Set(qwebrInstallRPackagesList)); - - // Install R packages one at a time (either silently or with a status update) - await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', qwebrShowStartupMessage); - - if (qwebrAutoloadRPackages) { - // Load R packages one at a time (either silently or with a status update) - await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', qwebrShowStartupMessage); - } - } - } -); - -// Stop timer -const initializeWebRTimerEnd = performance.now(); diff --git a/book/_extensions/coatless/webr/qwebr-document-history.js b/book/_extensions/coatless/webr/qwebr-document-history.js deleted file mode 100644 index df00091132..0000000000 --- a/book/_extensions/coatless/webr/qwebr-document-history.js +++ /dev/null @@ -1,110 +0,0 @@ -// Define a global storage and retrieval solution ---- - -// Store commands executed in R -globalThis.qwebrRCommandHistory = []; - -// Function to retrieve the command history -globalThis.qwebrFormatRHistory = function() { - return qwebrRCommandHistory.join("\n\n"); -} - -// Retrieve HTML Elements ---- - -// Get the command modal -const command_history_modal = document.getElementById("qwebr-history-modal"); - -// Get the button that opens the command modal -const command_history_btn = document.getElementById("qwebrRHistoryButton"); - -// Get the element that closes the command modal -const command_history_close_span = document.getElementById("qwebr-command-history-close-btn"); - -// Get the download button for r history information -const command_history_download_btn = document.getElementById("qwebr-download-history-btn"); - -// Plug in command history into modal/download button ---- - -// Function to populate the modal with command history -function populateCommandHistoryModal() { - document.getElementById("qwebr-command-history-contents").innerHTML = qwebrFormatRHistory() || "No commands have been executed yet."; -} - -// Function to format the current date and time to -// a string with the format YYYY-MM-DD-HH-MM-SS -function formatDateTime() { - const now = new Date(); - - const year = now.getFullYear(); - const day = String(now.getDate()).padStart(2, '0'); - const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`; -} - - -// Function to convert document title with datetime to a safe filename -function safeFileName() { - // Get the current page title - let pageTitle = document.title; - - // Combine the current page title with the current date and time - let pageNameWithDateTime = `Rhistory-${pageTitle}-${formatDateTime()}`; - - // Replace unsafe characters with safe alternatives - let safeFilename = pageNameWithDateTime.replace(/[\\/:\*\?! "<>\|]/g, '-'); - - return safeFilename; -} - - -// Function to download list contents as text file -function downloadRHistory() { - // Get the current page title + datetime and use it as the filename - const filename = `${safeFileName()}.R`; - - // Get the text contents of the R History list - const text = qwebrFormatRHistory(); - - // Create a new Blob object with the text contents - const blob = new Blob([text], { type: 'text/plain' }); - - // Create a new anchor element for the download - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = URL.createObjectURL(blob); - a.download = filename; - - // Append the anchor to the body, click it, and remove it - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); -} - -// Register event handlers ---- - -// When the user clicks the View R History button, open the command modal -command_history_btn.onclick = function() { - populateCommandHistoryModal(); - command_history_modal.style.display = "block"; -} - -// When the user clicks on (x), close the command modal -command_history_close_span.onclick = function() { - command_history_modal.style.display = "none"; -} - -// When the user clicks anywhere outside of the command modal, close it -window.onclick = function(event) { - if (event.target == command_history_modal) { - command_history_modal.style.display = "none"; - } -} - -// Add an onclick event listener to the download button so that -// the user can download the R history as a text file -command_history_download_btn.onclick = function() { - downloadRHistory(); -}; \ No newline at end of file diff --git a/book/_extensions/coatless/webr/qwebr-document-settings.js b/book/_extensions/coatless/webr/qwebr-document-settings.js deleted file mode 100644 index 70495cf667..0000000000 --- a/book/_extensions/coatless/webr/qwebr-document-settings.js +++ /dev/null @@ -1,26 +0,0 @@ -// Document level settings ---- - -// Determine if we need to install R packages -globalThis.qwebrInstallRPackagesList = [{{INSTALLRPACKAGESLIST}}]; - -// Specify possible locations to search for the repository -globalThis.qwebrPackageRepoURLS = [{{RPACKAGEREPOURLS}}]; - -// Check to see if we have an empty array, if we do set to skip the installation. -globalThis.qwebrSetupRPackages = !(qwebrInstallRPackagesList.indexOf("") !== -1); -globalThis.qwebrAutoloadRPackages = {{AUTOLOADRPACKAGES}}; - -// Display a startup message? -globalThis.qwebrShowStartupMessage = {{SHOWSTARTUPMESSAGE}}; -globalThis.qwebrShowHeaderMessage = {{SHOWHEADERMESSAGE}}; - -// Describe the webR settings that should be used -globalThis.qwebrCustomizedWebROptions = { - "baseURL": "{{BASEURL}}", - "serviceWorkerUrl": "{{SERVICEWORKERURL}}", - "homedir": "{{HOMEDIR}}", - "channelType": "{{CHANNELTYPE}}" -}; - -// Store cell data -globalThis.qwebrCellDetails = {{QWEBRCELLDETAILS}}; diff --git a/book/_extensions/coatless/webr/qwebr-document-status.js b/book/_extensions/coatless/webr/qwebr-document-status.js deleted file mode 100644 index 71441df408..0000000000 --- a/book/_extensions/coatless/webr/qwebr-document-status.js +++ /dev/null @@ -1,368 +0,0 @@ -// Declare startupMessageQWebR globally -globalThis.qwebrStartupMessage = document.createElement("p"); - -// Verify if OffScreenCanvas is supported -globalThis.qwebrOffScreenCanvasSupport = function() { - return typeof OffscreenCanvas !== 'undefined' -} - -// Function to set the button text -globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) { - document.querySelectorAll(".qwebr-button-run").forEach((btn) => { - btn.innerHTML = buttonText; - btn.disabled = !enableCodeButton; - }); -} - -// Function to update the status message in non-interactive cells -globalThis.qwebrUpdateStatusMessage = function(message) { - document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => { - elem.innerText = message; - }); -} - -// Function to update the status message -globalThis.qwebrUpdateStatusHeader = function(message) { - qwebrStartupMessage.innerHTML = ` - - ${message}`; -} - -// Function to return true if element is found, false if not -globalThis.qwebrCheckHTMLElementExists = function(selector) { - const element = document.querySelector(selector); - return !!element; -} - -// Function that detects whether reveal.js slides are present -globalThis.qwebrIsRevealJS = function() { - // If the '.reveal .slides' selector exists, RevealJS is likely present - return qwebrCheckHTMLElementExists('.reveal .slides'); -} - -// Initialize the Quarto sidebar element -function qwebrSetupQuartoSidebar() { - var newSideBarDiv = document.createElement('div'); - newSideBarDiv.id = 'quarto-margin-sidebar'; - newSideBarDiv.className = 'sidebar margin-sidebar'; - newSideBarDiv.style.top = '0px'; - newSideBarDiv.style.maxHeight = 'calc(0px + 100vh)'; - - return newSideBarDiv; -} - -// Position the sidebar in the document -function qwebrPlaceQuartoSidebar() { - // Get the reference to the element with id 'quarto-document-content' - var referenceNode = document.getElementById('quarto-document-content'); - - // Create the new div element - var newSideBarDiv = qwebrSetupQuartoSidebar(); - - // Insert the new div before the 'quarto-document-content' element - referenceNode.parentNode.insertBefore(newSideBarDiv, referenceNode); -} - -function qwebrPlaceMessageContents(content, html_location = "title-block-header", revealjs_location = "title-slide") { - - // Get references to header elements - const headerHTML = document.getElementById(html_location); - const headerRevealJS = document.getElementById(revealjs_location); - - // Determine where to insert the quartoTitleMeta element - if (headerHTML || headerRevealJS) { - // Append to the existing "title-block-header" element or "title-slide" div - (headerHTML || headerRevealJS).appendChild(content); - } else { - // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script - const monacoScript = document.getElementById("qwebr-monaco-editor-init"); - const header = document.createElement("header"); - header.setAttribute("id", "title-block-header"); - header.appendChild(content); - monacoScript.after(header); - } -} - - - -function qwebrOffScreenCanvasSupportWarningMessage() { - - // Verify canvas is supported. - if(qwebrOffScreenCanvasSupport()) return; - - // Create the main container div - var calloutContainer = document.createElement('div'); - calloutContainer.classList.add('callout', 'callout-style-default', 'callout-warning', 'callout-titled'); - - // Create the header div - var headerDiv = document.createElement('div'); - headerDiv.classList.add('callout-header', 'd-flex', 'align-content-center'); - - // Create the icon container div - var iconContainer = document.createElement('div'); - iconContainer.classList.add('callout-icon-container'); - - // Create the icon element - var iconElement = document.createElement('i'); - iconElement.classList.add('callout-icon'); - - // Append the icon element to the icon container - iconContainer.appendChild(iconElement); - - // Create the title container div - var titleContainer = document.createElement('div'); - titleContainer.classList.add('callout-title-container', 'flex-fill'); - titleContainer.innerText = 'Warning: Web Browser Does Not Support Graphing!'; - - // Append the icon container and title container to the header div - headerDiv.appendChild(iconContainer); - headerDiv.appendChild(titleContainer); - - // Create the body container div - var bodyContainer = document.createElement('div'); - bodyContainer.classList.add('callout-body-container', 'callout-body'); - - // Create the paragraph element for the body content - var paragraphElement = document.createElement('p'); - paragraphElement.innerHTML = 'This web browser does not have support for displaying graphs through the quarto-webr extension since it lacks an OffScreenCanvas. Please upgrade your web browser to one that supports OffScreenCanvas.'; - - // Append the paragraph element to the body container - bodyContainer.appendChild(paragraphElement); - - // Append the header div and body container to the main container div - calloutContainer.appendChild(headerDiv); - calloutContainer.appendChild(bodyContainer); - - // Append the main container div to the document depending on format - qwebrPlaceMessageContents(calloutContainer, "title-block-header"); - -} - - -// Function that attaches the document status message and diagnostics -function displayStartupMessage(showStartupMessage, showHeaderMessage) { - if (!showStartupMessage) { - return; - } - - // Create the outermost div element for metadata - const quartoTitleMeta = document.createElement("div"); - quartoTitleMeta.classList.add("quarto-title-meta"); - - // Create the first inner div element - const firstInnerDiv = document.createElement("div"); - firstInnerDiv.setAttribute("id", "qwebr-status-message-area"); - - // Create the second inner div element for "WebR Status" heading and contents - const secondInnerDiv = document.createElement("div"); - secondInnerDiv.setAttribute("id", "qwebr-status-message-title"); - secondInnerDiv.classList.add("quarto-title-meta-heading"); - secondInnerDiv.innerText = "WebR Status"; - - // Create another inner div for contents - const secondInnerDivContents = document.createElement("div"); - secondInnerDivContents.setAttribute("id", "qwebr-status-message-body"); - secondInnerDivContents.classList.add("quarto-title-meta-contents"); - - // Describe the WebR state - qwebrStartupMessage.innerText = "🟡 Loading..."; - qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text"); - // Add `aria-live` to auto-announce the startup status to screen readers - qwebrStartupMessage.setAttribute("aria-live", "assertive"); - - // Append the startup message to the contents - secondInnerDivContents.appendChild(qwebrStartupMessage); - - // Add a status indicator for COOP and COEP Headers if needed - if (showHeaderMessage) { - const crossOriginMessage = document.createElement("p"); - crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`; - crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header"); - secondInnerDivContents.appendChild(crossOriginMessage); - } - - // Combine the inner divs and contents - firstInnerDiv.appendChild(secondInnerDiv); - firstInnerDiv.appendChild(secondInnerDivContents); - quartoTitleMeta.appendChild(firstInnerDiv); - - // Place message on webpage - qwebrPlaceMessageContents(quartoTitleMeta); -} - -function qwebrAddCommandHistoryModal() { - // Create the modal div - var modalDiv = document.createElement('div'); - modalDiv.id = 'qwebr-history-modal'; - modalDiv.className = 'qwebr-modal'; - - // Create the modal content div - var modalContentDiv = document.createElement('div'); - modalContentDiv.className = 'qwebr-modal-content'; - - // Create the span for closing the modal - var closeSpan = document.createElement('span'); - closeSpan.id = 'qwebr-command-history-close-btn'; - closeSpan.className = 'qwebr-modal-close'; - closeSpan.innerHTML = '×'; - - // Create the h1 element for the modal - var modalH1 = document.createElement('h1'); - modalH1.textContent = 'R History Command Contents'; - - // Create an anchor element for downloading the Rhistory file - var downloadLink = document.createElement('a'); - downloadLink.href = '#'; - downloadLink.id = 'qwebr-download-history-btn'; - downloadLink.className = 'qwebr-download-btn'; - - // Create an 'i' element for the icon - var icon = document.createElement('i'); - icon.className = 'bi bi-file-code'; - - // Append the icon to the anchor element - downloadLink.appendChild(icon); - - // Add the text 'Download R History' to the anchor element - downloadLink.appendChild(document.createTextNode(' Download R History File')); - - // Create the pre for command history contents - var commandContentsPre = document.createElement('pre'); - commandContentsPre.id = 'qwebr-command-history-contents'; - commandContentsPre.className = 'qwebr-modal-content-code'; - - // Append the close span, h1, and history contents pre to the modal content div - modalContentDiv.appendChild(closeSpan); - modalContentDiv.appendChild(modalH1); - modalContentDiv.appendChild(downloadLink); - modalContentDiv.appendChild(commandContentsPre); - - // Append the modal content div to the modal div - modalDiv.appendChild(modalContentDiv); - - // Append the modal div to the body - document.body.appendChild(modalDiv); -} - -function qwebrRegisterRevealJSCommandHistoryModal() { - // Select the