Difference between revisions of "Widget:CodeExplorer"

From Coder Merlin
 
(3 intermediate revisions by the same user not shown)
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 58: 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) {
Line 72: 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 104: Line 225:
             }
             }


            let codeEditor<!--{$exerciseID|validate:int}-->;
             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 114: Line 234:
                     }
                     }
                 );
                 );
                 // 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}-->("<!--{$language}-->");
                 setCurrentLanguage<!--{$exerciseID|validate:int}-->("<!--{$language}-->");


                 // Determine if submit button is enabled, if not, disable
                 // Determine if CEG-ID is set
                 var isSubmitEnabled = '<!--{$codeExplorerGroupID|strip}-->'.length > 0;
                 const isCEGIDSet = '<!--{$codeExplorerGroupID|strip}-->'.length > 0;
                 if (isSubmitEnabled) {
 
                     var submitButton = $("#codeEditorSubmitButton<!--{$exerciseID|validate:int}-->");
                // Disable/enable buttons as appropriate
                     submitButton.prop("value", "Submit to <!--{$codeExplorerGroupID|strip}-->");              
                 if (isCEGIDSet) {
                     enableSubmitButton<!--{$exerciseID|validate:int}-->();
                    enableSyncButton<!--{$exerciseID|validate:int}-->();
                     enableBroadcastButton<!--{$exerciseID|validate:int}-->();
                 } else {
                 } else {
                     var submitButton = $("#codeEditorSubmitButton<!--{$exerciseID|validate:int}-->");
                     disableSubmitButton<!--{$exerciseID|validate:int}-->();
                     submitButton.attr("disabled", true);
                     disableSyncButton<!--{$exerciseID|validate:int}-->();
                     submitButton.css("background-color", "gray");
                     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 137: Line 265:


                     // Disable buttons
                     // Disable buttons
                     let executeButton = $("#codeEditorExecuteButton<!--{$exerciseID|validate:int}-->");
                     disableExecuteButton<!--{$exerciseID|validate:int}-->();
                     let submitButton = $("#codeEditorSubmitButton<!--{$exerciseID|validate:int}-->");
                     disableSubmitButton<!--{$exerciseID|validate:int}-->();
                     let executeButtonBackgroundColor = executeButton.css("background-color");
                     disableSyncButton<!--{$exerciseID|validate:int}-->();
                    let submitButtonBackgroundColor = submitButton.css("background-color");
                     disableBroadcastButton<!--{$exerciseID|validate:int}-->();
                    executeButton.attr("disabled", true);
                     executeButton.css("background-color", "gray");
                    submitButton.attr("disabled", true);
                    submitButton.css("background-color", "gray");


                     // Enable animation
                     // Enable animation
Line 181: 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 202: Line 327:
                                     "Internal error: " + textStatus + "<br/>" + errorThrown + "<br/>" +  
                                     "Internal error: " + textStatus + "<br/>" + errorThrown + "<br/>" +  
                                     "</span><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
                             executeButton.attr("disabled", false);
                             enableExecuteButton<!--{$exerciseID|validate:int}-->();
                             executeButton.css("background-color", executeButtonBackgroundColor);
                             if (isCEGIDSet) {
                            if (isSubmitEnabled) {
                                enableSubmitButton<!--{$exerciseID|validate:int}-->();
                                 submitButton.attr("disabled", false);
                                 enableSyncButton<!--{$exerciseID|validate:int}-->();
                                 submitButton.css("background-color", submitButtonBackgroundColor);
                                 enableBroadcastButton<!--{$exerciseID|validate:int}-->();
                             }  
                             }


                             // Disable animation
                             // Disable animation
                             controlPanel.className = "merlin-code-explorer-control-panel";
                             controlPanel.className = "merlin-code-explorer-control-panel";
                         }
                         }
                     });
                     }); // ajax
 
                    // Collect response data                                                                                                                                                                                                                                                             
                    response.done(function(responseObject) {
                        switch (event.target.submitter) {
                            case "execute":
                                let compilationStatus = responseObject.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 = responseObject.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;
                }); // submit function
                            case "submit":
                                let standardOutput = (typeof responseObject == "object") ? responseObject.standardOutput : "";
                                let standardError = (typeof responseObject == "object" && typeof responseObject.standardError == "string") ? "error: " + responseObject.standardError : "";
                                if (typeof responseObject == "object" && responseObject.timedOut) {
                                    standardError += "error: timed out\n";
                                }
                                $("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->").append(consoleToHTML(standardOutput));
                                $("#codeEditorExecutionOutput<!--{$exerciseID|validate:int}-->").
                                    append(markupWarningsAndErrorsHTML(consoleToHTML(standardError)));
                                break;
                        }
                                     
                        // Re-enable buttons
                        executeButton.attr("disabled", false);
                        executeButton.css("background-color", executeButtonBackgroundColor);
                        if (isSubmitEnabled) {
                            submitButton.attr("disabled", false);
                            submitButton.css("background-color", submitButtonBackgroundColor);
                        }


                        // Disable animation
                // Show Compilation button
                        controlPanel.className = "merlin-code-explorer-control-panel";
                      });
                });
                 $("#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 289: 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&nbsp;&nbsp;<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&nbsp;&nbsp;<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!")
}
}}