Difference between revisions of "Widget:CodeExplorer"
From Coder Merlin
(17 intermediate revisions by the same user not shown) | |||
Line 11: | Line 11: | ||
;theme: string: name of theme (which must be loaded via css) | ;theme: string: name of theme (which must be loaded via css) | ||
;readOnly: boolean: true if editing should be disabled | ;readOnly: boolean: true if editing should be disabled | ||
; | ;language: string: language for compiling and highlighting (which must be loaded via js) | ||
;initialCode: string: initial code to place in editor | ;initialCode: string: initial code to place in editor | ||
Line 27: | Line 27: | ||
|theme=vibrant-ink | |theme=vibrant-ink | ||
|readOnly=false | |readOnly=false | ||
| | |language=swift | ||
|initialCode=func sayHello() { | |initialCode=func sayHello() { | ||
print("Hello, World!") | print("Hello, World!") | ||
Line 35: | Line 35: | ||
</noinclude> | </noinclude> | ||
<includeonly> | <includeonly><form action="" id="codeEditorForm<!--{$exerciseID|validate:int}-->"> | ||
<form action="" id="codeEditorForm<!--{$exerciseID|validate:int}-->"> | |||
<div class="merlin-code-explorer-container"> | <div class="merlin-code-explorer-container"> | ||
<div class="merlin-code-explorer-banner"> | <div class="merlin-code-explorer-banner"> | ||
<img class="merlin-code-explorer-banner-merlin-icon" src="/wiki/resources/assets/MerlinRoundIcon.png" /> | <img class="merlin-code-explorer-banner-merlin-icon" src="/wiki/resources/assets/MerlinRoundIcon.png" /> | ||
<span class="merlin-code-explorer-banner-text">CoderMerlin™ Code Explorer: <!--{$experienceID}--> (<!--{$exerciseID}-->)</span> | <span class="merlin-code-explorer-banner-text">CoderMerlin™ Code Explorer: <!--{$experienceID}--> (<!--{$exerciseID}-->)</span> | ||
<span class="merlin-code-explorer-banner-text"><!--{$codeExplorerGroupID|strip}--></span> | |||
<span id="codeEditorStatusIndicator<!--{$exerciseID|validate:int}-->">🟢</span> | |||
<div class="dropdown"> | <div class="dropdown"> | ||
<button class="btn text-light dropdown-toggle py-0" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | <button class="btn text-light dropdown-toggle py-0" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||
Line 46: | Line 47: | ||
</button> | </button> | ||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> | <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> | ||
<div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('assembly');">Assembly</div> | |||
<div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('c');">C</div> | <div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('c');">C</div> | ||
<div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('cpp');">C++</div> | |||
<div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('java');">Java</div> | <div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('java');">Java</div> | ||
<div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('python');">Python</div> | <div class="dropdown-item" onclick="setCurrentLanguage<!--{$exerciseID|validate:int}-->('python');">Python</div> | ||
Line 56: | Line 59: | ||
<textarea id="codeEditorTextArea<!--{$exerciseID|validate:int}-->"><!--{$initialCode}--></textarea> | <textarea id="codeEditorTextArea<!--{$exerciseID|validate:int}-->"><!--{$initialCode}--></textarea> | ||
<script> | <script> | ||
let codeEditor<!--{$exerciseID|validate:int}-->; | |||
// Preserved background colors | |||
let executeButtonBackgroundColor<!--{$exerciseID|validate:int}-->; | |||
let submitButtonBackgroundColor<!--{$exerciseID|validate:int}-->; | |||
let syncButtonBackgroundColor<!--{$exerciseID|validate:int}-->; | |||
let broadcastButtonBackgroundColor<!--{$exerciseID|validate:int}-->; | |||
// Broadcast | |||
let wasChangedSinceBroadcast<!--{$exerciseID|validate:int}--> = 1; // We start at one to ensure that even an empty file is broadcast | |||
function onChange<!--{$exerciseID|validate:int}-->(codeMirrorInstance, changeObject) { | |||
wasChangedSinceBroadcast<!--{$exerciseID|validate:int}-->++; | |||
} | |||
// Language and mode | |||
let currentLanguageData<!--{$exerciseID|validate:int}--> = ""; | let currentLanguageData<!--{$exerciseID|validate:int}--> = ""; | ||
function setCurrentLanguage<!--{$exerciseID|validate:int}-->(language) { | function setCurrentLanguage<!--{$exerciseID|validate:int}-->(language) { | ||
let languageData = dataFromLanguage(language); | let languageData = dataFromLanguage(language); | ||
if (typeof languageData != "object") { | |||
alert("Unexpected language: " + language); | |||
} | |||
let mode = languageData[0]; | let mode = languageData[0]; | ||
let sourceLanguage = languageData[1]; | let sourceLanguage = languageData[1]; | ||
Line 67: | Line 88: | ||
codeEditor<!--{$exerciseID|validate:int}-->.setOption("mode", mode); | codeEditor<!--{$exerciseID|validate:int}-->.setOption("mode", mode); | ||
document.getElementById("codeExplorerIcon<!--{$exerciseID|validate:int}-->").src = iconURL; | document.getElementById("codeExplorerIcon<!--{$exerciseID|validate:int}-->").src = iconURL; | ||
} | |||
// Status indicator | |||
function setStatusIndicator<!--{$exerciseID|validate:int}-->(status) { | |||
let indicator = $("#codeEditorStatusIndicator<!--{$exerciseID|validate:int}-->").get(0); | |||
switch (status) { | |||
case "red": | |||
indicator.innerText = "🔴" | |||
break; | |||
case "yellow": | |||
indicator.innerText = "🟡" | |||
break; | |||
case "green": | |||
indicator.innerText = "🟢" | |||
break; | |||
} | |||
} | |||
// Live theater sync | |||
function syncToLiveTheater<!--{$exerciseID|validate:int}-->() { | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('yellow'); | |||
// Clear output | |||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->").empty(); | |||
$("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->").empty(); | |||
// Get results | |||
let username = "<!--{$userName}-->".toLowerCase(); | |||
let sessionID = "<!--{$sessionID}-->" | |||
let url = languageServerURL(); | |||
url += "codeExplorerGroups/" + "<!--{$codeExplorerGroupID|strip}-->/"; | |||
url += "experiences/" + "<!--{$experienceID}-->/" + "exercises/" + "<!--{$exerciseID}-->" + "/broadcast"; | |||
let response = $.ajax({ | |||
type: "GET", | |||
url, | |||
headers: { | |||
"username": username, | |||
"sessionID": sessionID | |||
}, | |||
dataType: "json", | |||
error: function(xmlhttprequest, textstatus, message) { | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('red'); | |||
}, | |||
success: function(data) { | |||
let sourceLanguage = Object.keys(data.sourceLanguage)[0]; | |||
let contents = data.sourceFiles[0].contents; | |||
// Set language | |||
setCurrentLanguage<!--{$exerciseID|validate:int}-->(sourceLanguage); | |||
// Set content (currently only first file) | |||
codeEditor<!--{$exerciseID|validate:int}-->.setValue(contents); | |||
// Set status | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('green'); | |||
}, | |||
timeout: 2500 // Should be less than repeat interval | |||
}); | |||
} | |||
function broadcastToLiveTheater<!--{$exerciseID|validate:int}-->() { | |||
// Only broadcast if changes have occurred since the previous broadcast | |||
if (wasChangedSinceBroadcast<!--{$exerciseID|validate:int}--> > 0) { | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('yellow'); | |||
let username = "<!--{$userName}-->".toLowerCase(); | |||
let sessionID = "<!--{$sessionID}-->" | |||
let url = (subdomain() == "stg") ? | |||
"https://language-server-stg.codermerlin.com/" : | |||
"https://language-server.codermerlin.com/"; | |||
url += "codeExplorerGroups/" + "<!--{$codeExplorerGroupID|strip}-->/"; | |||
url += "experiences/" + "<!--{$experienceID}-->/" + "exercises/" + "<!--{$exerciseID}-->" + "/broadcast"; | |||
let sourceLanguage = currentLanguageData<!--{$exerciseID|validate:int}-->[1]; | |||
let sourceFileSuffix = currentLanguageData<!--{$exerciseID|validate:int}-->[2]; | |||
let requestObject = { | |||
"sourceLanguage": {}, | |||
"sourceFiles": [] | |||
}; | |||
requestObject["sourceLanguage"][sourceLanguage] = {}; | |||
requestObject["sourceFiles"] = [{"path": "main" + "." + sourceFileSuffix, | |||
"contents": codeEditor<!--{$exerciseID|validate:int}-->.getValue()}]; | |||
let requestString = JSON.stringify(requestObject); | |||
let response = $.ajax({ | |||
type: "POST", | |||
url, | |||
headers: { | |||
"username": username, | |||
"sessionID": sessionID | |||
}, | |||
data: requestString, | |||
dataType: "json", | |||
contentType : "application/json", | |||
timeout: 2500, // Should be shorter than refresh interval | |||
error: function(xmlhttprequest, textstatus, message) { | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('red'); | |||
}, | |||
success: function(data) { | |||
// Clear changes (only if successful) | |||
wasChangedSinceBroadcast<!--{$exerciseID|validate:int}--> = 0; | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('green'); | |||
} | |||
}); | |||
} | |||
} | } | ||
Line 99: | Line 225: | ||
} | } | ||
window.addEventListener('load', (event) => { | window.addEventListener('load', (event) => { | ||
codeEditor<!--{$exerciseID|validate:int}--> = CodeMirror.fromTextArea(document.getElementById('codeEditorTextArea<!--{$exerciseID|validate:int}-->'), | codeEditor<!--{$exerciseID|validate:int}--> = CodeMirror.fromTextArea(document.getElementById('codeEditorTextArea<!--{$exerciseID|validate:int}-->'), | ||
Line 106: | Line 231: | ||
lineNumbers: "<!--{$lineNumbers|validate:boolean}-->", | lineNumbers: "<!--{$lineNumbers|validate:boolean}-->", | ||
theme: "<!--{$theme}-->", | theme: "<!--{$theme}-->", | ||
readOnly: "<!--{$readOnly | readOnly: "<!--{$readOnly}-->" | ||
} | } | ||
); | ); | ||
// Set size | // Set size | ||
codeEditor<!--{$exerciseID|validate:int}-->.setSize("<!--{$width}-->", "<!--{$height}-->"); | codeEditor<!--{$exerciseID|validate:int}-->.setSize("<!--{$width}-->", "<!--{$height}-->"); | ||
// Set language | // Set language | ||
setCurrentLanguage<!--{$exerciseID|validate:int}-->(" | setCurrentLanguage<!--{$exerciseID|validate:int}-->("<!--{$language}-->"); | ||
// Determine if CEG-ID is set | |||
const isCEGIDSet = '<!--{$codeExplorerGroupID|strip}-->'.length > 0; | |||
// | // Disable/enable buttons as appropriate | ||
if (isCEGIDSet) { | |||
enableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
enableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
enableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
} else { | } else { | ||
disableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
disableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
disableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
} | } | ||
// Monitor events | |||
codeEditor<!--{$exerciseID|validate:int}-->.on("change", onChange<!--{$exerciseID|validate:int}-->); | |||
// Attach handler to form | // Attach handler to form | ||
Line 133: | Line 265: | ||
// Disable buttons | // Disable buttons | ||
disableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
disableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
disableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
disableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
// Enable animation | // Enable animation | ||
Line 177: | Line 305: | ||
let requestString = JSON.stringify(requestObject); | let requestString = JSON.stringify(requestObject); | ||
setStatusIndicator<!--{$exerciseID|validate:int}-->('yellow'); | |||
let response = $.ajax({ | let response = $.ajax({ | ||
type: "POST", | type: "POST", | ||
Line 187: | Line 316: | ||
dataType: "json", | dataType: "json", | ||
contentType : "application/json", | contentType : "application/json", | ||
timeout: | timeout: 30000, | ||
error: function(jqXHR, textStatus, errorThrown) { | error: function(jqXHR, textStatus, errorThrown) { | ||
// Display error | // Display error | ||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->").append("<span class='merlin-code-explorer-combined-output-error'>" + | if (response.status == 503) { | ||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->").append("<span class='merlin-code-explorer-combined-output-warning'>" + | |||
"Sorry, server too busy. Please try again soon." + | |||
"</span><br/>"); | |||
} else { | |||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->").append("<span class='merlin-code-explorer-combined-output-error'>" + | |||
"Internal error: " + textStatus + "<br/>" + errorThrown + "<br/>" + | |||
"</span><br/>"); | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('red'); | |||
} | |||
}, | |||
success: function(data) { | |||
switch (event.target.submitter) { | |||
case "execute": | |||
let compilationStatus = data.compilationStatus; | |||
let compilationOutput = compilationStatus.standardOutput; | |||
let compilationError = compilationStatus.standardError; | |||
if (compilationStatus.timedOut) { | |||
compilationError += "error: timed out\n"; | |||
} | |||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->"). | |||
append(markupWarningsAndErrorsHTML(consoleToHTML(compilationError))); | |||
$("#codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->"). | |||
append(markupWarningsAndErrorsHTML(consoleToHTML(compilationOutput))); | |||
let executionStatus = data.executionStatus; | |||
let executionOutput = (typeof executionStatus == "object") ? executionStatus.standardOutput : ""; | |||
let executionError = (typeof executionStatus == "object") ? executionStatus.standardError : ""; | |||
if (typeof executionStatus == "object" && executionStatus.timedOut) { | |||
executionError += "error: timed out\n"; | |||
} | |||
$("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->").append(consoleToHTML(executionError)); | |||
$("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->"). | |||
append(markupRuntimeStandardOutput(consoleToHTML(executionOutput))); | |||
// Set compilation display | |||
const $button = $("#codeEditorForm<!--{$exerciseID|validate:int}--> .merlin-code-explorer-show-compilation-button"); | |||
const $output = $button.parent().siblings(".merlin-code-explorer-combined-output:first"); | |||
if (compilationStatus.terminationStatus == 0) { | |||
$output.slideUp(); | |||
$button.text("Show Compilation Output"); | |||
} else { | |||
$output.slideDown(); | |||
$button.text("Hide Compilation Output"); | |||
} | |||
break; | |||
case "submit": | |||
let standardOutput = (typeof data == "object") ? data.standardOutput : ""; | |||
let standardError = (typeof data == "object" && typeof data.standardError == "string") ? "error: " + data.standardError : ""; | |||
if (typeof data == "object" && data.timedOut) { | |||
standardError += "error: timed out\n"; | |||
} | |||
$("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->").append(consoleToHTML(standardOutput)); | |||
$("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->"). | |||
append(markupWarningsAndErrorsHTML(consoleToHTML(standardError))); | |||
break; | |||
} // switch | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('green'); | |||
}, // success | |||
complete: function(jqXHR, textStatus, errorThrown) { | |||
// Re-enable buttons | // Re-enable buttons | ||
enableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
if (isCEGIDSet) { | |||
enableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
enableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
enableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
} | } | ||
// Disable animation | // Disable animation | ||
controlPanel.className = "merlin-code-explorer-control-panel"; | controlPanel.className = "merlin-code-explorer-control-panel"; | ||
} | } | ||
}); | }); // ajax | ||
}); // submit function | |||
// Show Compilation button | |||
$("#codeEditorForm<!--{$exerciseID|validate:int}--> .merlin-code-explorer-show-compilation-button").click(function () { | $("#codeEditorForm<!--{$exerciseID|validate:int}--> .merlin-code-explorer-show-compilation-button").click(function () { | ||
const $output = $(this).parent().siblings(".merlin-code-explorer-combined-output:first"); | const $output = $(this).parent().siblings(".merlin-code-explorer-combined-output:first"); | ||
Line 261: | Line 406: | ||
} | } | ||
}); | }); | ||
// Broadcast button | |||
function enableBroadcastButton<!--{$exerciseID|validate:int}-->() { | |||
let broadcastButton = $("#codeEditorBroadcastButton<!--{$exerciseID|validate:int}-->"); | |||
broadcastButton.attr("disabled", false); | |||
if (broadcastButtonBackgroundColor<!--{$exerciseID|validate:int}--> != undefined) { | |||
broadcastButton.css("background-color", broadcastButtonBackgroundColor<!--{$exerciseID|validate:int}-->); | |||
} | |||
} | |||
function disableBroadcastButton<!--{$exerciseID|validate:int}-->() { | |||
let broadcastButton = $("#codeEditorBroadcastButton<!--{$exerciseID|validate:int}-->"); | |||
// Save original background color | |||
if (broadcastButtonBackgroundColor<!--{$exerciseID|validate:int}--> == undefined) { | |||
broadcastButtonBackgroundColor<!--{$exerciseID|validate:int}--> = broadcastButton.css("background-color"); | |||
} | |||
broadcastButton.attr("disabled", true); | |||
broadcastButton.css("background-color", "gray"); | |||
} | |||
// Sync button | |||
function enableSyncButton<!--{$exerciseID|validate:int}-->() { | |||
let syncButton = $("#codeEditorSyncButton<!--{$exerciseID|validate:int}-->"); | |||
syncButton.attr("disabled", false); | |||
if (syncButtonBackgroundColor<!--{$exerciseID|validate:int}--> != undefined) { | |||
syncButton.css("background-color", syncButtonBackgroundColor<!--{$exerciseID|validate:int}-->); | |||
} | |||
} | |||
function disableSyncButton<!--{$exerciseID|validate:int}-->() { | |||
let syncButton = $("#codeEditorSyncButton<!--{$exerciseID|validate:int}-->"); | |||
// Save original background color | |||
if (syncButtonBackgroundColor<!--{$exerciseID|validate:int}--> == undefined) { | |||
syncButtonBackgroundColor<!--{$exerciseID|validate:int}--> = syncButton.css("background-color"); | |||
} | |||
syncButton.attr("disabled", true); | |||
syncButton.css("background-color", "gray"); | |||
} | |||
// Execute button | |||
function enableExecuteButton<!--{$exerciseID|validate:int}-->() { | |||
let executeButton = $("#codeEditorExecuteButton<!--{$exerciseID|validate:int}-->"); | |||
executeButton.attr("disabled", false); | |||
if (executeButtonBackgroundColor<!--{$exerciseID|validate:int}--> != undefined) { | |||
executeButton.css("background-color", executeButtonBackgroundColor<!--{$exerciseID|validate:int}-->); | |||
} | |||
} | |||
function disableExecuteButton<!--{$exerciseID|validate:int}-->() { | |||
let executeButton = $("#codeEditorExecuteButton<!--{$exerciseID|validate:int}-->"); | |||
// Save original background color | |||
if (executeButtonBackgroundColor<!--{$exerciseID|validate:int}--> == undefined) { | |||
executeButtonBackgroundColor<!--{$exerciseID|validate:int}--> = executeButton.css("background-color"); | |||
} | |||
executeButton.attr("disabled", true); | |||
executeButton.css("background-color", "gray"); | |||
} | |||
// Submit button | |||
function disableSubmitButton<!--{$exerciseID|validate:int}-->() { | |||
let submitButton = $("#codeEditorSubmitButton<!--{$exerciseID|validate:int}-->"); | |||
// Save original background color | |||
if (submitButtonBackgroundColor<!--{$exerciseID|validate:int}--> == undefined) { | |||
submitButtonBackgroundColor<!--{$exerciseID|validate:int}--> = submitButton.css("background-color"); | |||
} | |||
submitButton.attr("disabled", true); | |||
submitButton.css("background-color", "gray"); | |||
} | |||
function enableSubmitButton<!--{$exerciseID|validate:int}-->() { | |||
let submitButton = $("#codeEditorSubmitButton<!--{$exerciseID|validate:int}-->"); | |||
submitButton.attr("disabled", false); | |||
if (submitButtonBackgroundColor<!--{$exerciseID|validate:int}--> != undefined) { | |||
submitButton.css("background-color", submitButtonBackgroundColor<!--{$exerciseID|validate:int}-->); | |||
} | |||
submitButton.prop("value", "Submit to <!--{$codeExplorerGroupID|strip}-->"); | |||
} | |||
// Live Theater button | |||
let isLiveTheaterSyncActive = false; | |||
let liveTheaterSyncIntervalId = 0; | |||
function turnOnLiveTheater<!--{$exerciseID|validate:int}-->() { | |||
if (isLiveTheaterSyncActive) { | |||
console.error("turnOnLiveTheater() invoked when already on."); | |||
return; | |||
} | |||
let syncIcon = $("#codeEditorSyncIcon<!--{$exerciseID|validate:int}-->").get(0); | |||
syncIcon.classList.add("spin"); | |||
syncToLiveTheater<!--{$exerciseID|validate:int}-->(); | |||
liveTheaterSyncIntervalId = setInterval(syncToLiveTheater<!--{$exerciseID|validate:int}-->, 3000); | |||
isLiveTheaterSyncActive = true; | |||
disableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
disableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
disableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
} | |||
function turnOffLiveTheater<!--{$exerciseID|validate:int}-->() { | |||
if (!isLiveTheaterSyncActive) { | |||
console.error("turnOffLiveTheater() invoked when already off."); | |||
return; | |||
} | |||
let syncIcon = $("#codeEditorSyncIcon<!--{$exerciseID|validate:int}-->").get(0); | |||
syncIcon.classList.remove("spin"); | |||
clearInterval(liveTheaterSyncIntervalId); | |||
isLiveTheaterSyncActive = false; | |||
enableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
enableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
enableBroadcastButton<!--{$exerciseID|validate:int}-->(); | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('green'); | |||
} | |||
$("#codeEditorForm<!--{$exerciseID|validate:int}--> .merlin-code-explorer-live-theater-sync-button").click(function () { | |||
if (isLiveTheaterSyncActive) { | |||
turnOffLiveTheater<!--{$exerciseID|validate:int}-->(); | |||
} else { | |||
turnOnLiveTheater<!--{$exerciseID|validate:int}-->(); | |||
} | |||
}); | |||
// Live Broadcast button | |||
let isLiveTheaterBroadcastActive = false; | |||
let liveTheaterBroadcastIntervalId = 0; | |||
function turnOnLiveBroadcast<!--{$exerciseID|validate:int}-->() { | |||
if (isLiveTheaterBroadcastActive) { | |||
console.error("turnOnLiveBroadcast() invoked when already on."); | |||
return; | |||
} | |||
let broadcastIcon = $("#codeEditorBroadcastIcon<!--{$exerciseID|validate:int}-->").get(0); | |||
broadcastIcon.classList.add("pulse"); | |||
broadcastToLiveTheater<!--{$exerciseID|validate:int}-->(); | |||
liveTheaterBroadcastIntervalId = setInterval(broadcastToLiveTheater<!--{$exerciseID|validate:int}-->, 3000); | |||
isLiveTheaterBroadcastActive = true; | |||
disableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
disableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
disableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
} | |||
function turnOffLiveBroadcast<!--{$exerciseID|validate:int}-->() { | |||
if (!isLiveTheaterBroadcastActive) { | |||
console.error("turnOffLiveBroadcast() invoked when already off."); | |||
return; | |||
} | |||
let broadcastIcon = $("#codeEditorBroadcastIcon<!--{$exerciseID|validate:int}-->").get(0); | |||
broadcastIcon.classList.remove("pulse"); | |||
clearInterval(liveTheaterBroadcastIntervalId); | |||
isLiveTheaterBroadcastActive = false; | |||
enableExecuteButton<!--{$exerciseID|validate:int}-->(); | |||
enableSubmitButton<!--{$exerciseID|validate:int}-->(); | |||
enableSyncButton<!--{$exerciseID|validate:int}-->(); | |||
setStatusIndicator<!--{$exerciseID|validate:int}-->('green'); | |||
} | |||
$("#codeEditorForm<!--{$exerciseID|validate:int}--> .merlin-code-explorer-live-theater-broadcast-button").click(function () { | |||
if (isLiveTheaterBroadcastActive) { | |||
turnOffLiveBroadcast<!--{$exerciseID|validate:int}-->(); | |||
} else { | |||
turnOnLiveBroadcast<!--{$exerciseID|validate:int}-->(); | |||
} | |||
}); | |||
}); | }); | ||
</script> | </script> | ||
</div> | </div> | ||
<div id="codeEditorControlPanel<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-control-panel"> | <div id="codeEditorControlPanel<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-control-panel"> | ||
<input id="codeEditorExecuteButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-execute-button" onclick="this.form.submitter = 'execute';" type="submit" value="Run" /> | <input id="codeEditorExecuteButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-button merlin-code-explorer-execute-button" onclick="this.form.submitter = 'execute';" type="submit" value="Run" /> | ||
<input id="codeEditorSubmitButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-submit-button" onclick="this.form.submitter = 'submit';" type="submit" value="Submit"/> | <input id="codeEditorSubmitButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-button merlin-code-explorer-submit-button" onclick="this.form.submitter = 'submit';" type="submit" value="Submit"/> | ||
<button class="merlin-code-explorer-show-compilation-button" type="button">Hide Compilation Output</button> | <button class="merlin-code-explorer-button merlin-code-explorer-show-compilation-button" type="button">Hide Compilation Output</button> | ||
<button id="codeEditorSyncButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-button merlin-code-explorer-live-theater-sync-button" type="button">Sync <div id="codeEditorSyncIcon<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-live-theater-sync-button-icon"><i class="fa fa-sync"></i></div></button> | |||
<button id="codeEditorBroadcastButton<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-button merlin-code-explorer-live-theater-broadcast-button" type="button">Broadcast <div id="codeEditorBroadcastIcon<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-live-theater-broadcast-button-icon"><i class="fa fa-wifi"></i></div></button> | |||
</div> | </div> | ||
<div id="codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-combined-output"></div> | <div id="codeEditorCompilerOutput<!--{$exerciseID|validate:int}-->" class="merlin-code-explorer-combined-output"></div> |
Latest revision as of 08:06, 9 March 2023
Parameters:
- userName
- string: The current user's username
- sessionID
- string: The ID of the current user's session
- experienceID
- string: The experienceID of the page from which the widget is invoked
- codeExplorerGroupID
- string: The code explorer group. If empty, the submit button will be disabled.
- exerciseID
- integer: exercise id for editor, must be unique per page
- width
- integer|string: percentage (as string, e.g. "100%" or integer size in pixels), null for no change (full width)
- height
- integer|string: percentage (as string, e.g. "100%" or integer size in pixels), null for no change (~10 lines)
- lineNumbers
- boolean: true to display line numbers
- theme
- string: name of theme (which must be loaded via css)
- readOnly
- boolean: true if editing should be disabled
- language
- string: language for compiling and highlighting (which must be loaded via js)
- initialCode
- string: initial code to place in editor
Example:
{{#widget:CodeExplorer |userName=john-williams |sessionID=qh0ubrrme911kcg7db0i0ec6lct94h7f |experienceID=W1020.23 |codeExplorerGroupID=WTRS-8527 |exerciseID=10 |width=null |height=null |lineNumbers=true |theme=vibrant-ink |readOnly=false |language=swift |initialCode=func sayHello() { print("Hello, World!") } }}