Loading static/js/exercises.api.mjs +11 −16 Original line number Diff line number Diff line Loading @@ -154,13 +154,13 @@ class ExerciseExecutionContext { prepareWith(command, keepVisible) { return new Promise((resolve, _) => { function prepareAndResolve() { async function prepareAndResolve() { // Actually prepare and resolve the promise (we get the resolve() function via the closure) if(command !== null) { runCommand(command) await runCommand(command) } if (!keepVisible) { runCommand("clear") await runCommand("clear") } resolve() } Loading @@ -174,7 +174,6 @@ class ExerciseExecutionContext { if (getTerminalContents().length > 0) { clearInterval(intervalId) prepareAndResolve() resolve() } }, 500) } Loading Loading @@ -229,9 +228,9 @@ class ExerciseExecutionContext { * @returns An object that has the method "hasOutput" */ verify(command) { if(command) { runCommand(command) } // If a command is provided, store the returned promise (which is resolved as soon as the command was run), // if no command is provided, directly start with a resolved promise. const commandPromise = command ? runCommand(command) : Promise.resolve() const verifyHandler = this._verifyHandler return { Loading @@ -246,9 +245,8 @@ class ExerciseExecutionContext { * @param {Function|String|Object} param String or list of strings to exactly match, or predicate function */ hasOutput(param) { // Wait for 200ms to ensure that the VM has processed the input and written some // output to the terminal setTimeout(() => { // Wait until the VM has processed the input and written some output to the terminal commandPromise.then(() => { const terminalContents = getTerminalContents() const latestEntry = terminalContents[terminalContents.length - 1] Loading @@ -267,7 +265,7 @@ class ExerciseExecutionContext { const result = !!predicate(latestEntry.output) verifyHandler(result) }, 200) }) }, /** Loading @@ -280,13 +278,10 @@ class ExerciseExecutionContext { * @param {Function|String} param String to exactly match, or predicate function */ commandWas(param) { // Wait for 200ms to ensure that the VM has processed the input and written some // output to the terminal setTimeout(() => { commandPromise.then(() => { const terminalContents = getTerminalContents() const latestEntry = terminalContents[terminalContents.length - 1] // Poor man's switch-case: depending on the "typeof param", a different verification predicate is chosen const predicate = { "function": param, Loading @@ -296,7 +291,7 @@ class ExerciseExecutionContext { const result = !!predicate(latestEntry.input) verifyHandler(result) }, 200) }) } } } Loading static/js/jslinux.api.mjs +30 −2 Original line number Diff line number Diff line Loading @@ -108,9 +108,10 @@ function sanitize(line) { * input line), the returned results won't contain all the output or the input line * - Empty lines are ignored: Even if a command legitimately outputs an empty line, it will be ignored. * * @param includeLastInteraction If the last interaction (which only consists of a prompt, with nothing executed yet) should be included * @returns List of objects of the form {"prompt": "...", "input": "...", "output": ["...", "..."]} */ export function getTerminalContents() { export function getTerminalContents(includeLastInteraction) { const contents = [] let currentInteraction = {} Loading @@ -128,9 +129,33 @@ export function getTerminalContents() { } }) if(includeLastInteraction) { contents.push(currentInteraction) } return contents } /** * Returns the current prompt as soon as one is shown */ function prompt() { return new Promise((resolve)=>{ const intervalId = setInterval(checkAndResolve, 100) function checkAndResolve() { const lastInteraction = getTerminalContents(true).at(-1) if(lastInteraction.prompt && !lastInteraction.input.trim()) { clearInterval(intervalId) resolve(prompt) } } checkAndResolve() }) } /** * Calls the given handler function without parameters whenever the user presses the key enter * @param {Function} handler A handler function Loading @@ -148,7 +173,7 @@ export function onEnterPressed(handler) { * The command will then be executed, and the output will be shown in the terminal * @param {string} command A command to execute */ export function runCommand(command) { export async function runCommand(command) { const textarea = document.querySelector(".term_textarea") // We append \n to simulate the Enter press textarea.value = command + "\n" Loading @@ -156,6 +181,9 @@ export function runCommand(command) { // Now we just have to trigger an input event to activate the JSLinux event handler, see // https://thewebdev.info/2021/05/02/how-to-programmatically-trigger-a-change-event-on-an-input-with-javascript/ textarea.dispatchEvent(new Event("input")) // And waid until a prompt is shown (-> until the command is executed) await prompt() } /** Loading Loading
static/js/exercises.api.mjs +11 −16 Original line number Diff line number Diff line Loading @@ -154,13 +154,13 @@ class ExerciseExecutionContext { prepareWith(command, keepVisible) { return new Promise((resolve, _) => { function prepareAndResolve() { async function prepareAndResolve() { // Actually prepare and resolve the promise (we get the resolve() function via the closure) if(command !== null) { runCommand(command) await runCommand(command) } if (!keepVisible) { runCommand("clear") await runCommand("clear") } resolve() } Loading @@ -174,7 +174,6 @@ class ExerciseExecutionContext { if (getTerminalContents().length > 0) { clearInterval(intervalId) prepareAndResolve() resolve() } }, 500) } Loading Loading @@ -229,9 +228,9 @@ class ExerciseExecutionContext { * @returns An object that has the method "hasOutput" */ verify(command) { if(command) { runCommand(command) } // If a command is provided, store the returned promise (which is resolved as soon as the command was run), // if no command is provided, directly start with a resolved promise. const commandPromise = command ? runCommand(command) : Promise.resolve() const verifyHandler = this._verifyHandler return { Loading @@ -246,9 +245,8 @@ class ExerciseExecutionContext { * @param {Function|String|Object} param String or list of strings to exactly match, or predicate function */ hasOutput(param) { // Wait for 200ms to ensure that the VM has processed the input and written some // output to the terminal setTimeout(() => { // Wait until the VM has processed the input and written some output to the terminal commandPromise.then(() => { const terminalContents = getTerminalContents() const latestEntry = terminalContents[terminalContents.length - 1] Loading @@ -267,7 +265,7 @@ class ExerciseExecutionContext { const result = !!predicate(latestEntry.output) verifyHandler(result) }, 200) }) }, /** Loading @@ -280,13 +278,10 @@ class ExerciseExecutionContext { * @param {Function|String} param String to exactly match, or predicate function */ commandWas(param) { // Wait for 200ms to ensure that the VM has processed the input and written some // output to the terminal setTimeout(() => { commandPromise.then(() => { const terminalContents = getTerminalContents() const latestEntry = terminalContents[terminalContents.length - 1] // Poor man's switch-case: depending on the "typeof param", a different verification predicate is chosen const predicate = { "function": param, Loading @@ -296,7 +291,7 @@ class ExerciseExecutionContext { const result = !!predicate(latestEntry.input) verifyHandler(result) }, 200) }) } } } Loading
static/js/jslinux.api.mjs +30 −2 Original line number Diff line number Diff line Loading @@ -108,9 +108,10 @@ function sanitize(line) { * input line), the returned results won't contain all the output or the input line * - Empty lines are ignored: Even if a command legitimately outputs an empty line, it will be ignored. * * @param includeLastInteraction If the last interaction (which only consists of a prompt, with nothing executed yet) should be included * @returns List of objects of the form {"prompt": "...", "input": "...", "output": ["...", "..."]} */ export function getTerminalContents() { export function getTerminalContents(includeLastInteraction) { const contents = [] let currentInteraction = {} Loading @@ -128,9 +129,33 @@ export function getTerminalContents() { } }) if(includeLastInteraction) { contents.push(currentInteraction) } return contents } /** * Returns the current prompt as soon as one is shown */ function prompt() { return new Promise((resolve)=>{ const intervalId = setInterval(checkAndResolve, 100) function checkAndResolve() { const lastInteraction = getTerminalContents(true).at(-1) if(lastInteraction.prompt && !lastInteraction.input.trim()) { clearInterval(intervalId) resolve(prompt) } } checkAndResolve() }) } /** * Calls the given handler function without parameters whenever the user presses the key enter * @param {Function} handler A handler function Loading @@ -148,7 +173,7 @@ export function onEnterPressed(handler) { * The command will then be executed, and the output will be shown in the terminal * @param {string} command A command to execute */ export function runCommand(command) { export async function runCommand(command) { const textarea = document.querySelector(".term_textarea") // We append \n to simulate the Enter press textarea.value = command + "\n" Loading @@ -156,6 +181,9 @@ export function runCommand(command) { // Now we just have to trigger an input event to activate the JSLinux event handler, see // https://thewebdev.info/2021/05/02/how-to-programmatically-trigger-a-change-event-on-an-input-with-javascript/ textarea.dispatchEvent(new Event("input")) // And waid until a prompt is shown (-> until the command is executed) await prompt() } /** Loading