better question display

This commit is contained in:
2024-09-19 13:36:16 -06:00
parent 225a65b53c
commit a388076a65
8 changed files with 156 additions and 131 deletions

View File

@@ -20,6 +20,7 @@ import {
import { Spinner } from "@/components/Spinner";
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
import ClientOnly from "@/components/ClientOnly";
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
export default function EditAssignment({
moduleName,
@@ -82,10 +83,12 @@ export default function EditAssignment({
</div>
</div>
<ClientOnly>
<AssignmentButtons
moduleName={moduleName}
assignmentName={assignmentName}
/>
<SuspenseAndErrorHandling>
<AssignmentButtons
moduleName={moduleName}
assignmentName={assignmentName}
/>
</SuspenseAndErrorHandling>
</ClientOnly>
</div>
);

View File

@@ -14,6 +14,7 @@ import {
useCreateCanvasPageMutation,
} from "@/hooks/canvas/canvasPageHooks";
import EditPageButtons from "./EditPageButtons";
import ClientOnly from "@/components/ClientOnly";
export default function EditPage({
moduleName,
@@ -73,11 +74,13 @@ export default function EditPage({
</div>
</div>
{settings.canvasId && (
<EditPageButtons
pageName={pageName}
moduleName={moduleName}
courseCanvasId={settings.canvasId}
/>
<ClientOnly>
<EditPageButtons
pageName={pageName}
moduleName={moduleName}
courseCanvasId={settings.canvasId}
/>
</ClientOnly>
)}
</div>
);

View File

@@ -1,3 +1,4 @@
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
import { Spinner } from "@/components/Spinner";
import {
useCanvasPagesQuery,
@@ -5,7 +6,11 @@ import {
useDeleteCanvasPageMutation,
useUpdateCanvasPageMutation,
} from "@/hooks/canvas/canvasPageHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { usePageQuery } from "@/hooks/localCourse/pageHooks";
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
import { getCourseUrl } from "@/services/urlUtils";
import Link from "next/link";
import React from "react";
export default function EditPageButtons({
@@ -17,6 +22,8 @@ export default function EditPageButtons({
moduleName: string;
courseCanvasId: number;
}) {
const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery();
const { data: page } = usePageQuery(moduleName, pageName);
const { data: canvasPages } = useCanvasPagesQuery(courseCanvasId);
const createPageInCanvas = useCreateCanvasPageMutation(courseCanvasId);
@@ -31,15 +38,8 @@ export default function EditPageButtons({
deletePageInCanvas.isPending;
return (
<div className="p-5 flex flex-row gap-x-3">
{pageInCanvas && (
<a
target="_blank"
href={`https://snow.instructure.com/courses/${courseCanvasId}/pages/${pageInCanvas.page_id}`}
>
View Page In Canvas
</a>
)}
<div className="p-5 flex justify-end flex-row gap-x-3">
{requestIsPending && <Spinner />}
{!pageInCanvas && (
<button
onClick={() => createPageInCanvas.mutate(page)}
@@ -61,15 +61,27 @@ export default function EditPageButtons({
Update in Canvas
</button>
)}
{pageInCanvas && (
<a
className="btn"
target="_blank"
href={`${baseCanvasUrl}/courses/${settings.canvasId}/pages/${pageInCanvas.url}`}
>
View in Canvas
</a>
)}
{pageInCanvas && (
<button
className="btn-danger"
onClick={() => deletePageInCanvas.mutate(pageInCanvas.page_id)}
disabled={requestIsPending}
>
Delete from Canvas
</button>
)}
{requestIsPending && <Spinner />}
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Go Back
</Link>
</div>
);
}

View File

@@ -7,17 +7,7 @@ import {
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
import { useEffect, useState } from "react";
import QuizPreview from "./QuizPreview";
import {
useAddQuizToCanvasMutation,
useCanvasQuizzesQuery,
useDeleteQuizFromCanvasMutation,
} from "@/hooks/canvas/canvasQuizHooks";
import { Spinner } from "@/components/Spinner";
import { baseCanvasUrl, canvasApi } from "@/services/canvas/canvasServiceUtils";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { getCourseUrl } from "@/services/urlUtils";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
import Link from "next/link";
import { QuizButtons } from "./QuizButton";
const helpString = `QUESTION REFERENCE
---
@@ -60,6 +50,7 @@ this is a matching question
^ left answer - right dropdown
^ other thing - another option`;
export default function EditQuiz({
moduleName,
quizName,
@@ -125,66 +116,3 @@ export default function EditQuiz({
</div>
);
}
function QuizButtons({
moduleName,
quizName,
toggleHelp,
}: {
quizName: string;
moduleName: string;
toggleHelp: () => void;
}) {
const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery();
const { data: canvasQuizzes } = useCanvasQuizzesQuery();
const { data: quiz } = useQuizQuery(moduleName, quizName);
const addToCanvas = useAddQuizToCanvasMutation();
const deleteFromCanvas = useDeleteQuizFromCanvasMutation();
const quizInCanvas = canvasQuizzes.find((c) => c.title === quizName);
return (
<div className="p-5 flex flex-row justify-between">
<div>
<button onClick={toggleHelp}>Toggle Help</button>
</div>
<div className="flex flex-row gap-3 justify-end">
{(addToCanvas.isPending || deleteFromCanvas.isPending) && <Spinner />}
{quizInCanvas && !quizInCanvas.published && (
<div className="text-rose-300 my-auto">Not Published</div>
)}
{!quizInCanvas && (
<button
disabled={addToCanvas.isPending}
onClick={() => addToCanvas.mutate(quiz)}
>
Add to canvas....
</button>
)}
{quizInCanvas && (
<a
className="btn"
target="_blank"
href={`${baseCanvasUrl}/courses/${settings.canvasId}/quizzes/${quizInCanvas.id}`}
>
View in Canvas
</a>
)}
{quizInCanvas && (
<button
className="btn-danger"
disabled={deleteFromCanvas.isPending}
onClick={() => deleteFromCanvas.mutate(quizInCanvas.id)}
>
Delete from Canvas
</button>
)}
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Go Back
</Link>
</div>
</div>
);
}

View File

@@ -0,0 +1,71 @@
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
import { Spinner } from "@/components/Spinner";
import { useCanvasQuizzesQuery, useAddQuizToCanvasMutation, useDeleteQuizFromCanvasMutation } from "@/hooks/canvas/canvasQuizHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { useQuizQuery } from "@/hooks/localCourse/quizHooks";
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
import { getCourseUrl } from "@/services/urlUtils";
import Link from "next/link";
export function QuizButtons({
moduleName,
quizName,
toggleHelp,
}: {
quizName: string;
moduleName: string;
toggleHelp: () => void;
}) {
const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery();
const { data: canvasQuizzes } = useCanvasQuizzesQuery();
const { data: quiz } = useQuizQuery(moduleName, quizName);
const addToCanvas = useAddQuizToCanvasMutation();
const deleteFromCanvas = useDeleteQuizFromCanvasMutation();
const quizInCanvas = canvasQuizzes.find((c) => c.title === quizName);
return (
<div className="p-5 flex flex-row justify-between">
<div>
<button onClick={toggleHelp}>Toggle Help</button>
</div>
<div className="flex flex-row gap-3 justify-end">
{(addToCanvas.isPending || deleteFromCanvas.isPending) && <Spinner />}
{quizInCanvas && !quizInCanvas.published && (
<div className="text-rose-300 my-auto">Not Published</div>
)}
{!quizInCanvas && (
<button
disabled={addToCanvas.isPending}
onClick={() => addToCanvas.mutate(quiz)}
>
Add to canvas....
</button>
)}
{quizInCanvas && (
<a
className="btn"
target="_blank"
href={`${baseCanvasUrl}/courses/${settings.canvasId}/quizzes/${quizInCanvas.id}`}
>
View in Canvas
</a>
)}
{quizInCanvas && (
<button
className="btn-danger"
disabled={deleteFromCanvas.isPending}
onClick={() => deleteFromCanvas.mutate(quizInCanvas.id)}
>
Delete from Canvas
</button>
)}
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Go Back
</Link>
</div>
</div>
);
}

View File

@@ -8,7 +8,7 @@ import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
return (
<div style={{ overflow: "scroll", height: "100%" }}>
<div style={{ overflow: "scroll", height: "100%" }}>
<div className="columns-2">
<div className="text-end">Name</div>
<div>{quiz.name}</div>
@@ -41,12 +41,14 @@ export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
{quiz.description}
</div>
<hr />
{quiz.questions.map((question, i) => (
<QuizQuestionPreview
key={quizQuestionMarkdownUtils.toMarkdown(question)}
question={question}
/>
))}
<div className="p-3 rounded-md bg-slate-950 m-5 flex flex-col gap-3">
{quiz.questions.map((question, i) => (
<QuizQuestionPreview
key={quizQuestionMarkdownUtils.toMarkdown(question)}
question={question}
/>
))}
</div>
<br />
<br />
<br />
@@ -62,10 +64,16 @@ export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
return (
<div className="rounded">
<div>Points: {question.points}</div>
<div>Type: {question.questionType}</div>
<div className="rounded bg-slate-900 px-2">
<div className="flex flex-row justify-between text-slate-400">
<div>{question.questionType}</div>
<div className="">
{question.points} {question.points === 1 ? " Point" : " Points"}
</div>
</div>
<div
className="ms-4 mb-2"
dangerouslySetInnerHTML={{ __html: markdownToHTMLSafe(question.text) }}
></div>
{question.questionType === QuestionType.MATCHING && (
@@ -73,10 +81,10 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
{question.answers.map((answer) => (
<div
key={JSON.stringify(answer)}
className="mx-3 mb-1 bg-dark px-2 rounded border flex row"
className="mx-3 mb-1 bg-dark rounded border border-slate-600 flex flex-row"
>
<div className="col text-right my-auto p-1">{answer.text} - </div>
<div className="col my-auto">{answer.matchedText}</div>
<div className="text-right my-auto">{answer.text} - </div>
<div className="">{answer.matchedText}</div>
</div>
))}
{question.matchDistractors.map((distractor) => (
@@ -94,32 +102,27 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
{question.answers.map((answer) => (
<div
key={JSON.stringify(answer)}
className="mx-3 mb-1 bg-dark px-2 rounded flex flex-row border"
className="mx-3 mb-1 bg-dark rounded border-slate-700 flex flex-row border"
>
{answer.correct ? (
<svg
style={{ width: "1em" }}
className="me-1 my-auto"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M4 12.6111L8.92308 17.5L20 6.5"
stroke="green"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
) : (
<div className="mr-1 my-auto" style={{ width: "1em" }}>
{question.questionType === QuestionType.MULTIPLE_ANSWERS && (
<span>[ ]</span>
)}
</div>
)}
<div className="w-8 flex flex-col justify-center">
{answer.correct ? (
<svg className="h-6" viewBox="0 0 24 24" fill="none">
<path
d="M4 12.6111L8.92308 17.5L20 6.5"
stroke="green"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
) : question.questionType === QuestionType.MULTIPLE_ANSWERS ? (
<span className="mx-auto">{"[ ]"}</span>
) : (
<div></div>
)}
</div>
<div
className="markdownQuizAnswerPreview p-1"
className="markdownQuizAnswerPreview"
dangerouslySetInnerHTML={{
__html: markdownToHTMLSafe(answer.text),
}}

View File

@@ -119,4 +119,9 @@ select {
.collapsible.expand {
max-height: 100vh;
}
.markdownQuizAnswerPreview p:last-child {
@apply p-0 m-0 ;
}

View File

@@ -14,7 +14,7 @@ export const SuspenseAndErrorHandling: FC<{ children: ReactNode }> = ({
<ErrorBoundary
onReset={reset}
fallbackRender={(props) => {
toast.error(getErrorMessage(props.error))
toast.error(getErrorMessage(props.error));
return (
<div className="text-center">
<div className="p-3">{getErrorMessage(props.error)}</div>