From dfd9b11e1a6bcc75bae02767f404b04b9fb8c4a7 Mon Sep 17 00:00:00 2001 From: jorg-vr Date: Thu, 19 Oct 2023 09:46:36 +0200 Subject: [PATCH] Always init tutor links --- app/assets/javascripts/file_viewer.ts | 69 +++++++++++++++++ .../{pythia_submission.ts => tutor.ts} | 76 +------------------ .../renderers/feedback_table_renderer.rb | 33 ++++++++ app/helpers/renderers/pythia_renderer.rb | 33 +------- app/javascript/packs/pythia_submission.js | 7 +- app/javascript/packs/submission.js | 5 ++ 6 files changed, 116 insertions(+), 107 deletions(-) create mode 100644 app/assets/javascripts/file_viewer.ts rename app/assets/javascripts/{pythia_submission.ts => tutor.ts} (66%) diff --git a/app/assets/javascripts/file_viewer.ts b/app/assets/javascripts/file_viewer.ts new file mode 100644 index 0000000000..a5dd372ec1 --- /dev/null +++ b/app/assets/javascripts/file_viewer.ts @@ -0,0 +1,69 @@ +import { showInfoModal } from "./modal"; +import { fetch } from "utilities"; +import { html } from "lit"; + +function showInlineFile(name: string, content: string): void { + showInfoModal(name, html`
${content}
`); +} + +function showRealFile(name: string, activityPath: string, filePath: string): void { + const path = activityPath + "/" + filePath; + const random = Math.floor(Math.random() * 10000 + 1); + showInfoModal( + html`${name} `, + html`
Loading...
` + ); + + fetch(path, { + method: "GET" + }).then(response => { + if (response.ok) { + response.text().then(data => { + let lines = data.split("\n"); + const maxLines = 99; + if (lines.length > maxLines) { + lines = lines.slice(0, maxLines); + lines.push("..."); + } + + const table = document.createElement("table"); + table.className = "external-file"; + for (let i = 0; i < lines.length; i++) { + const tr = document.createElement("tr"); + + const number = document.createElement("td"); + number.className = "line-nr"; + number.textContent = (i === maxLines) ? "" : (i + 1).toString(); + tr.appendChild(number); + + const line = document.createElement("td"); + line.className = "line"; + // textContent is safe, html is not executed + line.textContent = lines[i]; + tr.appendChild(line); + table.appendChild(tr); + } + const fileView = document.getElementById(`file-${random}`); + fileView.innerHTML = ""; + fileView.appendChild(table); + }); + } + }); +} +export function initFileViewers(activityPath: string): void { + document.querySelectorAll("a.file-link").forEach(l => l.addEventListener("click", e => { + const link = e.currentTarget as HTMLLinkElement; + const fileName = link.innerText; + const tc = link.closest(".testcase.contains-file") as HTMLDivElement; + if (tc === null) { + return; + } + const files = JSON.parse(tc.dataset.files); + const file = files[fileName]; + if (file.location === "inline") { + showInlineFile(fileName, file.content); + } else if (file.location === "href") { + showRealFile(fileName, activityPath, file.content); + } + })); +} diff --git a/app/assets/javascripts/pythia_submission.ts b/app/assets/javascripts/tutor.ts similarity index 66% rename from app/assets/javascripts/pythia_submission.ts rename to app/assets/javascripts/tutor.ts index c08e28ef5b..fd11f11aa7 100644 --- a/app/assets/javascripts/pythia_submission.ts +++ b/app/assets/javascripts/tutor.ts @@ -1,12 +1,11 @@ import fscreen from "fscreen"; -import { showInfoModal } from "./modal"; import { fetch } from "utilities"; -import { html } from "lit"; +import { showInfoModal } from "modal"; +import { html } from "lit/development"; -function initPythiaSubmissionShow(submissionCode: string, activityPath: string): void { +export function initTutor(submissionCode: string): void { function init(): void { initTutorLinks(); - initFileViewers(activityPath); if (document.querySelectorAll(".tutormodal").length == 1) { initFullScreen(); } else { @@ -52,73 +51,6 @@ function initPythiaSubmissionShow(submissionCode: string, activityPath: string): })); } - function initFileViewers(activityPath: string): void { - document.querySelectorAll("a.file-link").forEach(l => l.addEventListener("click", e => { - const link = e.currentTarget as HTMLLinkElement; - const fileName = link.innerText; - const tc = link.closest(".testcase.contains-file") as HTMLDivElement; - if (tc === null) { - return; - } - const files = JSON.parse(tc.dataset.files); - const file = files[fileName]; - if (file.location === "inline") { - showInlineFile(fileName, file.content); - } else if (file.location === "href") { - showRealFile(fileName, activityPath, file.content); - } - })); - } - - function showInlineFile(name: string, content: string): void { - showInfoModal(name, html`
${content}
`); - } - - function showRealFile(name: string, activityPath: string, filePath: string): void { - const path = activityPath + "/" + filePath; - const random = Math.floor(Math.random() * 10000 + 1); - showInfoModal( - html`${name} `, - html`
Loading...
` - ); - - fetch(path, { - method: "GET" - }).then(response => { - if (response.ok) { - response.text().then(data => { - let lines = data.split("\n"); - const maxLines = 99; - if (lines.length > maxLines) { - lines = lines.slice(0, maxLines); - lines.push("..."); - } - - const table = document.createElement("table"); - table.className = "external-file"; - for (let i = 0; i < lines.length; i++) { - const tr = document.createElement("tr"); - - const number = document.createElement("td"); - number.className = "line-nr"; - number.textContent = (i === maxLines) ? "" : (i + 1).toString(); - tr.appendChild(number); - - const line = document.createElement("td"); - line.className = "line"; - // textContent is safe, html is not executed - line.textContent = lines[i]; - tr.appendChild(line); - table.appendChild(tr); - } - const fileView = document.getElementById(`file-${random}`); - fileView.innerHTML = ""; - fileView.appendChild(table); - }); - } - }); - } - function initFullScreen(): void { fscreen.addEventListener("fullscreenchange", resizeFullScreen); @@ -220,5 +152,3 @@ function initPythiaSubmissionShow(submissionCode: string, activityPath: string): init(); } - -export { initPythiaSubmissionShow }; diff --git a/app/helpers/renderers/feedback_table_renderer.rb b/app/helpers/renderers/feedback_table_renderer.rb index 631d520e4b..9eb2134d53 100644 --- a/app/helpers/renderers/feedback_table_renderer.rb +++ b/app/helpers/renderers/feedback_table_renderer.rb @@ -29,6 +29,7 @@ def initialize(submission, user) def parse if @result.present? + tutor_init @builder.div(class: 'feedback-table', 'data-exercise_id': @exercise.id) do if @result[:messages].present? @builder.div(class: 'feedback-table-messages') do @@ -408,4 +409,36 @@ def safe(html) sanitize html end end + + def tutor_init + # Initialize tutor javascript + @builder.script do + escaped = escape_javascript(@code.strip) + @builder << 'dodona.ready.then(function() {' + @builder << "document.body.append(document.getElementById('tutor'));" + @builder << "dodona.initTutor(\"#{escaped}\");});" + end + + # Tutor HTML + @builder.div(id: 'tutor', class: 'tutormodal') do + @builder.div(id: 'info-modal', class: 'modal fade', 'data-backdrop': true, tabindex: -1) do + @builder.div(class: 'modal-dialog modal-xl modal-fullscreen-lg-down tutor') do + @builder.div(class: 'modal-content') do + @builder.div(class: 'modal-header') do + @builder.h4(class: 'modal-title') {} + @builder.div(class: 'icons') do + @builder.button(id: 'fullscreen-button', type: 'button', class: 'btn btn-icon') do + @builder.i('', class: 'mdi mdi-fullscreen') + end + @builder.button(type: 'button', class: 'btn btn-icon', 'data-bs-dismiss': 'modal') do + @builder.i('', class: 'mdi mdi-close') + end + end + end + @builder.div(class: 'modal-body') {} + end + end + end + end + end end diff --git a/app/helpers/renderers/pythia_renderer.rb b/app/helpers/renderers/pythia_renderer.rb index 0a4d15ee67..eaa3dcd5e1 100644 --- a/app/helpers/renderers/pythia_renderer.rb +++ b/app/helpers/renderers/pythia_renderer.rb @@ -2,7 +2,7 @@ class PythiaRenderer < FeedbackTableRenderer include ActionView::Helpers::JavaScriptHelper def parse - tutor_init + file_viewer_init super end @@ -74,36 +74,11 @@ def testcase(tc) ## custom methods - def tutor_init - # Initialize tutor javascript + def file_viewer_init + # Initialize file viewers @builder.script do - escaped = escape_javascript(@code.strip) @builder << 'dodona.ready.then(function() {' - @builder << "document.body.append(document.getElementById('tutor'));" - @builder << "var code = \"#{escaped}\";" - @builder << "dodona.initPythiaSubmissionShow(code, '#{activity_path(nil, @exercise)}');});" - end - - # Tutor HTML - @builder.div(id: 'tutor', class: 'tutormodal') do - @builder.div(id: 'info-modal', class: 'modal fade', 'data-backdrop': true, tabindex: -1) do - @builder.div(class: 'modal-dialog modal-xl modal-fullscreen-lg-down tutor') do - @builder.div(class: 'modal-content') do - @builder.div(class: 'modal-header') do - @builder.h4(class: 'modal-title') {} - @builder.div(class: 'icons') do - @builder.button(id: 'fullscreen-button', type: 'button', class: 'btn btn-icon') do - @builder.i('', class: 'mdi mdi-fullscreen') - end - @builder.button(type: 'button', class: 'btn btn-icon', 'data-bs-dismiss': 'modal') do - @builder.i('', class: 'mdi mdi-close') - end - end - end - @builder.div(class: 'modal-body') {} - end - end - end + @builder << "dodona.initFileViewers('#{activity_path(nil, @exercise)}');});" end end diff --git a/app/javascript/packs/pythia_submission.js b/app/javascript/packs/pythia_submission.js index 7d7956f8f4..b821cc58ec 100644 --- a/app/javascript/packs/pythia_submission.js +++ b/app/javascript/packs/pythia_submission.js @@ -1,6 +1,3 @@ -import { initPythiaSubmissionShow } from "pythia_submission.ts"; +import { initFileViewers } from "file_viewer"; -window.dodona.initPythiaSubmissionShow = initPythiaSubmissionShow; - -// will automatically bind to window.iFrameResize() -require("iframe-resizer"); // eslint-disable-line no-undef +window.dodona.initFileViewers = initFileViewers; diff --git a/app/javascript/packs/submission.js b/app/javascript/packs/submission.js index cf8d3a78e9..b1757cc902 100644 --- a/app/javascript/packs/submission.js +++ b/app/javascript/packs/submission.js @@ -4,6 +4,7 @@ import { attachClipboard } from "copy"; import { evaluationState } from "state/Evaluations"; import codeListing from "code_listing"; import { annotationState } from "state/Annotations"; +import { initTutor } from "tutor"; window.dodona.initSubmissionShow = initSubmissionShow; window.dodona.codeListing = codeListing; @@ -14,3 +15,7 @@ window.dodona.initSubmissionHistory = initSubmissionHistory; window.dodona.setEvaluationId = id => evaluationState.id = id; window.dodona.setAnnotationVisibility = visibility => annotationState.visibility = visibility; window.dodona.showLastTab = showLastTab; +window.dodona.initTutor = initTutor; + +// will automatically bind to window.iFrameResize() +require("iframe-resizer"); // eslint-disable-line no-undef