-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* fix #15: CodeMirror -> Monaco * use Monaco v0.37.1 * Monaco: v0.37.1 -> v0.31.0 * make startup message more accessible * Fix canvas issues * Add a way for monaco to dynamically resize Implementation based on: microsoft/monaco-editor#794 (comment) * Disable scrolling past end of cell * Disable the minimap * Cleanup spaces * Write a for more comments throughout the file * Improve the editor space * Lowercase webR * Slight wording tweaks * lint demo qmd file --------- Co-authored-by: James J Balamuta <[email protected]>
- Loading branch information
1 parent
7c605ee
commit 2cc9dcb
Showing
4 changed files
with
130 additions
and
38 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 |
---|---|---|
@@ -1,74 +1,155 @@ | ||
<button class="btn btn-default btn-webr" disabled type="button" id="webr-run-button-{{WEBRCOUNTER}}">Loading webR...</button> | ||
<button class="btn btn-default btn-webr" disabled type="button" id="webr-run-button-{{WEBRCOUNTER}}">Loading | ||
webR...</button> | ||
<div id="webr-editor-{{WEBRCOUNTER}}"></div> | ||
<div id="webr-code-output-{{WEBRCOUNTER}}"><pre style="visibility: hidden"></pre></div> | ||
<div id="webr-code-output-{{WEBRCOUNTER}}" aria-live="assertive"> | ||
<pre style="visibility: hidden"></pre> | ||
</div> | ||
<script type="module"> | ||
// Retrieve webR code cell information | ||
const runButton = document.getElementById("webr-run-button-{{WEBRCOUNTER}}"); | ||
const outputDiv = document.getElementById("webr-code-output-{{WEBRCOUNTER}}"); | ||
const editorDiv = document.getElementById("webr-editor-{{WEBRCOUNTER}}"); | ||
|
||
const editor = CodeMirror((elt) => { | ||
elt.style.border = "1px solid #eee"; | ||
elt.style.height = "auto"; | ||
editorDiv.append(elt); | ||
},{ | ||
value: `{{WEBRCODE}}`, | ||
lineNumbers: true, | ||
mode: "r", | ||
theme: "light default", | ||
viewportMargin: Infinity, | ||
// Add a light grey outline around the code editor | ||
editorDiv.style.border = "1px solid #eee"; | ||
|
||
// Load the Monaco Editor and create an instance | ||
let editor; | ||
require(['vs/editor/editor.main'], function () { | ||
editor = monaco.editor.create(editorDiv, { | ||
value: `{{WEBRCODE}}`, | ||
language: 'r', | ||
theme: 'vs-light', | ||
automaticLayout: true, // TODO: Could be problematic for slide decks | ||
scrollBeyondLastLine: false, | ||
minimap: { | ||
enabled: false | ||
}, | ||
fontSize: '17.5rem', // Bootstrap is 1 rem | ||
renderLineHighlight: "none", // Disable current line highlighting | ||
hideCursorInOverviewRuler: true // Remove cursor indictor in right hand side scroll bar | ||
}); | ||
|
||
// Add a keydown event listener for Shift+Enter using the addCommand method | ||
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, function () { | ||
// Code to run when Shift+Enter is pressed | ||
executeCode(editor.getValue()); | ||
}); | ||
|
||
// Add a keydown event listener for Ctrl+Enter to run selected code | ||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () { | ||
// Get the selected text from the editor | ||
const selectedText = editor.getModel().getValueInRange(editor.getSelection()); | ||
// Code to run when Ctrl+Enter is pressed (run selected code) | ||
executeCode(selectedText); | ||
}); | ||
|
||
// Dynamically modify the height of the editor window if new lines are added. | ||
let ignoreEvent = false; | ||
const updateHeight = () => { | ||
const contentHeight = editor.getContentHeight(); | ||
// We're avoiding a width change | ||
//editorDiv.style.width = `${width}px`; | ||
editorDiv.style.height = `${contentHeight}px`; | ||
try { | ||
ignoreEvent = true; | ||
|
||
// The key to resizing is this call | ||
editor.layout(); | ||
} finally { | ||
ignoreEvent = false; | ||
} | ||
}; | ||
|
||
// Register an on change event for when new code is added to the editor window | ||
editor.onDidContentSizeChange(updateHeight); | ||
|
||
// Manually re-update height to account for the content we inserted into the call | ||
updateHeight(); | ||
}); | ||
|
||
runButton.onclick = async () => { | ||
// Function to execute the code (accepts code as an argument) | ||
async function executeCode(codeToRun) { | ||
// Disable run button for code cell active | ||
runButton.disabled = true; | ||
|
||
// Create a canvas variable for graphics | ||
let canvas = undefined; | ||
|
||
// Initialize webR | ||
await globalThis.webR.init(); | ||
|
||
// Setup a webR canvas | ||
await webR.evalRVoid("canvas(width={{WIDTH}}, height={{HEIGHT}})"); | ||
const result = await webRCodeShelter.captureR(editor.getValue(), { | ||
|
||
// Capture output data from evaluating the code | ||
const result = await webRCodeShelter.captureR(codeToRun, { | ||
withAutoprint: true, | ||
captureStreams: true, | ||
captureConditions: false, | ||
env: webR.objs.emptyEnv, | ||
}); | ||
|
||
// Start attempting to parse the result data | ||
try { | ||
|
||
// Stop creating images | ||
await webR.evalRVoid("dev.off()"); | ||
|
||
// Merge output streams of STDOUT and STDErr (messages and errors are combined.) | ||
const out = result.output.filter( | ||
evt => evt.type == "stdout" || evt.type == "stderr" | ||
).map((evt) => evt.data).join("\n"); | ||
|
||
// Clean the state | ||
const msgs = await webR.flush(); | ||
|
||
// Output each image stored | ||
msgs.forEach(msg => { | ||
if (msg.type === "canvasExec"){ | ||
if (msg.type === "canvasExec") { | ||
if (!canvas) { | ||
canvas = document.createElement("canvas"); | ||
canvas.setAttribute("width", 2 * {{WIDTH}}); | ||
canvas.setAttribute("height", 2 * {{HEIGHT}}); | ||
canvas.style.width="700px"; | ||
canvas.style.display="block"; | ||
canvas.style.margin="auto"; | ||
canvas.style.width = "700px"; | ||
canvas.style.display = "block"; | ||
canvas.style.margin = "auto"; | ||
} | ||
Function(`this.getContext("2d").${msg.data}`).bind(canvas)(); | ||
} | ||
}); | ||
|
||
// Nullify the outputDiv of content | ||
outputDiv.innerHTML = ""; | ||
|
||
// Design an output object for messages | ||
const pre = document.createElement("pre"); | ||
if (/\S/.test(out)) { | ||
// Display results as text | ||
const code = document.createElement("code"); | ||
code.innerText = out; | ||
pre.appendChild(code); | ||
} else { | ||
// If nothing is present, hide the element. | ||
pre.style.visibility = "hidden"; | ||
} | ||
outputDiv.appendChild(pre); | ||
|
||
// Place the graphics on the canvas | ||
if (canvas) { | ||
const p = document.createElement("p"); | ||
p.appendChild(canvas); | ||
outputDiv.appendChild(p); | ||
} | ||
} finally { | ||
// Clean up the remaining code | ||
webRCodeShelter.purge(); | ||
runButton.disabled = false; | ||
} | ||
} | ||
</script> | ||
|
||
// Add a click event listener to the run button | ||
runButton.onclick = function () { | ||
executeCode(editor.getValue()); | ||
}; | ||
</script> |
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 |
---|---|---|
@@ -1,18 +1,26 @@ | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"> | ||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css" /> | ||
<style> | ||
.CodeMirror pre { | ||
.monaco-editor pre { | ||
background-color: unset !important; | ||
} | ||
|
||
.btn-webr { | ||
background-color: #EEEEEE; | ||
border-bottom-left-radius: 0; | ||
border-bottom-right-radius: 0; | ||
} | ||
</style> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/r/r.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/min/vs/loader.js"></script> | ||
<script type="module"> | ||
|
||
// Configure the Monaco Editor's loader | ||
require.config({ | ||
paths: { | ||
'vs': 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' | ||
} | ||
}); | ||
|
||
|
||
// Start a timer | ||
const initializeWebRTimerStart = performance.now(); | ||
|
||
|
@@ -47,6 +55,8 @@ | |
var startupMessageWebR = document.createElement("p"); | ||
startupMessageWebR.innerText = "🟡 Loading..."; | ||
startupMessageWebR.setAttribute("id", "startup"); | ||
// Add `aria-live` to auto-announce the startup status to screen readers | ||
startupMessageWebR.setAttribute("aria-live", "assertive"); | ||
|
||
// Put everything together | ||
secondInnerDivContents.appendChild(startupMessageWebR); | ||
|
@@ -70,7 +80,7 @@ | |
|
||
// Retrieve the webr.mjs | ||
import { WebR } from "https://webr.r-wasm.org/v0.1.1/webr.mjs"; | ||
|
||
// Populate WebR options with defaults or new values based on | ||
// webr meta | ||
globalThis.webR = new WebR({ | ||
|
@@ -106,4 +116,4 @@ | |
// If initialized, switch to a green light | ||
startupMessageWebR.innerText = "🟢 Ready!" | ||
} | ||
</script> | ||
</script> |
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