From 0f7a753f430cbdcd326190563fdc58494bab4261 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 8 Nov 2023 14:37:14 +0200 Subject: [PATCH] Allow putting a box around the iframes --- .../ExerciseTask/ExerciseTaskEditor.tsx | 2 +- .../moocfi/ExerciseBlock/ExerciseTask.tsx | 3 +- .../PeerReviewView/PeerReviewViewImpl.tsx | 3 +- .../moocfi/ExerciseBlock/index.tsx | 568 +++++++++--------- .../example-exercise/src/pages/iframe.tsx | 16 +- .../playground-views/PlaygroundPreview.tsx | 14 +- .../submissions/id/SubmissionIFrame.tsx | 2 +- .../src/pages/playground-views.tsx | 6 +- .../main-frontend/src/pages/playground.tsx | 2 +- services/quizzes/src/pages/iframe.tsx | 16 +- .../src/components/MessageChannelIFrame.tsx | 48 +- 11 files changed, 332 insertions(+), 348 deletions(-) diff --git a/services/cms/src/blocks/ExerciseTask/ExerciseTaskEditor.tsx b/services/cms/src/blocks/ExerciseTask/ExerciseTaskEditor.tsx index 40b994af1b1f..f81385526893 100644 --- a/services/cms/src/blocks/ExerciseTask/ExerciseTaskEditor.tsx +++ b/services/cms/src/blocks/ExerciseTask/ExerciseTaskEditor.tsx @@ -223,7 +223,7 @@ const ExerciseTaskEditor: React.FC< exerciseTaskId={attributes.id} onPrivateSpecChange={(x) => setAttributes({ private_spec: x })} privateSpec={privateSpecToPostToIframe} - url={`${url}?width=${narrowContainerWidthPx}`} + url={url} /> )} diff --git a/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/ExerciseTask.tsx b/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/ExerciseTask.tsx index 034a3f1ec9ec..0a02ac7bf5a2 100644 --- a/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/ExerciseTask.tsx +++ b/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/ExerciseTask.tsx @@ -9,7 +9,6 @@ import { CourseMaterialExerciseTask } from "../../../../shared-module/bindings" import LoginStateContext from "../../../../shared-module/contexts/LoginStateContext" import { IframeState } from "../../../../shared-module/exercise-service-protocol-types" import { baseTheme } from "../../../../shared-module/styles" -import { narrowContainerWidthPx } from "../../../../shared-module/styles/constants" import ExerciseTaskIframe from "./ExerciseTaskIframe" @@ -63,7 +62,7 @@ const ExerciseTask: React.FC> = ({ > model_solution_spec: course_material_exercise_task.model_solution_spec, }, }} - url={`${course_material_exercise_task.exercise_iframe_url}?width=${narrowContainerWidthPx}`} + url={course_material_exercise_task.exercise_iframe_url ?? ""} setAnswer={null} title={t("exercise-task-content", { "exercise-number": exerciseNumber + 1, diff --git a/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/index.tsx b/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/index.tsx index d1dcbea69296..c32194bf8b5a 100644 --- a/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/index.tsx +++ b/services/course-material/src/components/ContentRenderer/moocfi/ExerciseBlock/index.tsx @@ -235,309 +235,321 @@ const ExerciseBlock: React.FC< const gradingState = getCourseMaterialExercise.data.exercise_status?.grading_progress return ( - {/* Exercises are so important part of the pages that we will use section to make it easy-to-find + + {/* Exercises are so important part of the pages that we will use section to make it easy-to-find for screenreader users */} -
-
- -
- -

+
+
+
-
+

- {t("label-exercise")}: -

+
+ {t("label-exercise")}: +
+
+ {getCourseMaterialExercise.data.exercise.name} +
+

+
- {getCourseMaterialExercise.data.exercise.name} + {isExam && points === null ? ( + <> + {t("max-points")}: {getCourseMaterialExercise.data.exercise.score_maximum} + + ) : ( + <> + {t("points-label")}: +
+ {points ?? 0}/{getCourseMaterialExercise.data.exercise.score_maximum} + + )}
- -
-
- {isExam && points === null ? ( - <> - {t("max-points")}: {getCourseMaterialExercise.data.exercise.score_maximum} - - ) : ( - <> - {t("points-label")}: -
- {points ?? 0}/{getCourseMaterialExercise.data.exercise.score_maximum} - - )}
- -
- - {exerciseDeadline && - (Date.now() < exerciseDeadline.getTime() ? ( - = exerciseDeadline.getTime()}> - {t("deadline")} - - - ) : ( - - {t("Deadline-passed-n-days-ago", { days: dateDiffInDays(exerciseDeadline) })} - - ))} - - {getCourseMaterialExercise.data.peer_review_config && gradingState && reviewingStage && ( - - )} - {/* Reviewing stage seems to be undefined at least for exams */} - {reviewingStage !== "PeerReview" && - reviewingStage !== "SelfReview" && - getCourseMaterialExercise.data.current_exercise_slide.exercise_tasks - .sort((a, b) => a.order_number - b.order_number) - .map((task) => ( - - setAnswers((prev) => { - const answers = new Map(prev) - answers.set(task.id, answer) - return answers - }) - } - postThisStateToIFrame={postThisStateToIFrame?.find( - (x) => x.exercise_task_id === task.id, - )} - canPostSubmission={getCourseMaterialExercise.data.can_post_submission} - exerciseNumber={getCourseMaterialExercise.data.exercise.order_number} - /> - ))} - {reviewingStage === "PeerReview" && ( - - )} - {reviewingStage === "WaitingForPeerReviews" && } +
- {getCourseMaterialExercise.data.can_post_submission && - !userOnWrongLanguageVersion && - !inSubmissionView && ( - - )} - {inSubmissionView && - getCourseMaterialExercise.data.exercise.needs_peer_review && - exerciseSlideSubmissionId && - (reviewingStage === "WaitingForPeerReviews" || - reviewingStage === "ReviewedAndLocked") && ( - - )} - {inSubmissionView && - (reviewingStage === "NotStarted" || reviewingStage === undefined) && ( -
- {isExam && ( -
{ + queryClient.setQueryData( + queryUniqueKey, + (old: CourseMaterialExercise | undefined) => { + if (!old) { + // eslint-disable-next-line i18next/no-literal-string + throw new Error("No CourseMaterialExercise found") + } + return produce(old, (draft: CourseMaterialExercise) => { + res.exercise_task_submission_results.forEach( + (et_submission_result) => { + // Set previous submission so that it can be restored if the user tries the exercise again without reloading the page first + const receivedExerciseTaskSubmission = + et_submission_result.submission + const draftExerciseTask = + draft.current_exercise_slide.exercise_tasks.find((et) => { + return ( + et.id === + et_submission_result.submission.exercise_task_id + ) + }) + if (draftExerciseTask) { + draftExerciseTask.previous_submission = + receivedExerciseTaskSubmission + } + // Additional check to make sure we're not accidentally leaking gradings in exams from this endpoint + if (isExam && et_submission_result.grading !== null) { + // eslint-disable-next-line i18next/no-literal-string + throw new Error("Exams should have hidden gradings") + } + }, + ) + }) + }, + ) + }, + }, + ) + }} + > + {t("submit-button")} + + )} + {inSubmissionView && + getCourseMaterialExercise.data.exercise.needs_peer_review && + exerciseSlideSubmissionId && + (reviewingStage === "WaitingForPeerReviews" || + reviewingStage === "ReviewedAndLocked") && ( + + )} + {inSubmissionView && + (reviewingStage === "NotStarted" || reviewingStage === undefined) && ( +
+ {isExam && ( +
+ + +
{t("exam-submission-has-been-saved-help-text")}
+
+ )} + {!ranOutOfTries && ( +
- )} - {!ranOutOfTries && ( - - )} - {needsPeerReview && ( - - )} + > + {t("try-again")} + + )} + {needsPeerReview && ( + + )} +
+ )} + {postSubmissionMutation.isError && ( + + )} + {limit_number_of_tries && maxTries !== null && triesRemaining !== null && ( +
+ {t("tries-remaining-n", { n: triesRemaining })}
)} - {postSubmissionMutation.isError && ( - - )} - {limit_number_of_tries && maxTries !== null && triesRemaining !== null && ( -
- {t("tries-remaining-n", { n: triesRemaining })} -
- )} - {!loginState.isLoading && !loginState.signedIn && ( -
{t("please-log-in-to-answer-exercise")}
- )} + {!loginState.isLoading && !loginState.signedIn && ( +
{t("please-log-in-to-answer-exercise")}
+ )} +
-
-
+ +
) } diff --git a/services/example-exercise/src/pages/iframe.tsx b/services/example-exercise/src/pages/iframe.tsx index 3a4d80636515..560769333761 100644 --- a/services/example-exercise/src/pages/iframe.tsx +++ b/services/example-exercise/src/pages/iframe.tsx @@ -1,5 +1,3 @@ -import { css } from "@emotion/css" -import { useRouter } from "next/router" import React, { useState } from "react" import ReactDOM from "react-dom" import { useTranslation } from "react-i18next" @@ -42,12 +40,6 @@ export type State = const Iframe: React.FC> = () => { const { i18n } = useTranslation() const [state, setState] = useState(null) - const router = useRouter() - const rawMaxWidth = router?.query?.width - let maxWidth: number | null = 500 - if (rawMaxWidth) { - maxWidth = Number(rawMaxWidth) - } const port = useExerciseServiceParentConnection((messageData) => { if (forgivingIsSetStateMessage(messageData)) { @@ -87,13 +79,7 @@ const Iframe: React.FC> = () => { return ( -
+
diff --git a/services/main-frontend/src/components/page-specific/playground-views/PlaygroundPreview.tsx b/services/main-frontend/src/components/page-specific/playground-views/PlaygroundPreview.tsx index e8b43b2bd5a4..f26b3b460ddf 100644 --- a/services/main-frontend/src/components/page-specific/playground-views/PlaygroundPreview.tsx +++ b/services/main-frontend/src/components/page-specific/playground-views/PlaygroundPreview.tsx @@ -184,7 +184,13 @@ const PlaygroundPreview: React.FC = ({
-
+
{serviceInfoQuery.data && isValidServiceInfo && serviceInfoQuery.data && @@ -192,7 +198,7 @@ const PlaygroundPreview: React.FC = ({ <> {currentView === "exercise-editor" && ( = ({ /> = ({ /> > return ( { console.log(messageContainer) }} diff --git a/services/main-frontend/src/pages/playground-views.tsx b/services/main-frontend/src/pages/playground-views.tsx index c498e428a928..a689b68fc5bf 100644 --- a/services/main-frontend/src/pages/playground-views.tsx +++ b/services/main-frontend/src/pages/playground-views.tsx @@ -672,7 +672,7 @@ const IframeViewPlayground: React.FC> = () => { <> {currentView === "exercise-editor" && ( > = () => { }} /> > = () => { }} /> > = () => { } try { // eslint-disable-next-line i18next/no-literal-string - const newUrl = new URL(exampleUrl + `?width=${exampleWidth}`) + const newUrl = new URL(exampleUrl) setCombinedUrl(newUrl.toString()) setInvalidUrl(false) } catch (error) { diff --git a/services/quizzes/src/pages/iframe.tsx b/services/quizzes/src/pages/iframe.tsx index 2217bc2bccd8..7435a25a0c87 100644 --- a/services/quizzes/src/pages/iframe.tsx +++ b/services/quizzes/src/pages/iframe.tsx @@ -1,5 +1,3 @@ -import { css } from "@emotion/css" -import { useRouter } from "next/router" import React, { useState } from "react" import ReactDOM from "react-dom" import { useTranslation } from "react-i18next" @@ -63,12 +61,6 @@ export type State = const IFrame: React.FC> = () => { const { i18n } = useTranslation() const [state, setState] = useState(null) - const router = useRouter() - const rawMaxWidth = router?.query?.width - let maxWidth: number | null = 500 - if (rawMaxWidth) { - maxWidth = Number(rawMaxWidth) - } const port = useExerciseServiceParentConnection((messageData) => { if (forgivingIsSetStateMessage(messageData)) { @@ -198,13 +190,7 @@ const IFrame: React.FC> = () => { return ( -
+
diff --git a/shared-module/src/components/MessageChannelIFrame.tsx b/shared-module/src/components/MessageChannelIFrame.tsx index fa7f987f87e2..687d9ad59dd4 100644 --- a/shared-module/src/components/MessageChannelIFrame.tsx +++ b/shared-module/src/components/MessageChannelIFrame.tsx @@ -15,7 +15,7 @@ import { } from "../exercise-service-protocol-types.guard" import useMessageChannel from "../hooks/useMessageChannel" -import BreakFromCentered, { BreakFromCenteredProps } from "./Centering/BreakFromCentered" +import { BreakFromCenteredProps } from "./Centering/BreakFromCentered" interface MessageChannelIFrameProps { url: string @@ -35,7 +35,7 @@ const MessageChannelIFrame: React.FC< url, postThisStateToIFrame, onMessageFromIframe, - breakFromCenteredProps, + title, showBorders = false, disableSandbox = false, @@ -185,34 +185,30 @@ const MessageChannelIFrame: React.FC< } return ( - // We have to force the iframe to take the full width of the page because the iframe protocol requires it - -
+