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