-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
28810e3
commit 2e98d42
Showing
16 changed files
with
1,446 additions
and
927 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// Supported Evaluation Types for Context | ||
globalThis.EvalTypes = Object.freeze({ | ||
Interactive: 'interactive', | ||
Setup: 'setup', | ||
Output: 'output', | ||
}); | ||
|
||
// 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 = '<i class="fa-solid fa-arrows-rotate"></i>'; | ||
|
||
// 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 = '<i class="fa-regular fa-copy"></i>'; | ||
|
||
// 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// 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}`); | ||
// 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( | ||
`<i class="fa-solid fa-play qwebr-icon-run-code"></i> <span>Run Code</span>`, | ||
true | ||
); | ||
} | ||
); |
Oops, something went wrong.