mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 15:48:32 -06:00
better question display
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
|||||||
import { Spinner } from "@/components/Spinner";
|
import { Spinner } from "@/components/Spinner";
|
||||||
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
|
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
|
||||||
import ClientOnly from "@/components/ClientOnly";
|
import ClientOnly from "@/components/ClientOnly";
|
||||||
|
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
||||||
|
|
||||||
export default function EditAssignment({
|
export default function EditAssignment({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -82,10 +83,12 @@ export default function EditAssignment({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<AssignmentButtons
|
<SuspenseAndErrorHandling>
|
||||||
moduleName={moduleName}
|
<AssignmentButtons
|
||||||
assignmentName={assignmentName}
|
moduleName={moduleName}
|
||||||
/>
|
assignmentName={assignmentName}
|
||||||
|
/>
|
||||||
|
</SuspenseAndErrorHandling>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
useCreateCanvasPageMutation,
|
useCreateCanvasPageMutation,
|
||||||
} from "@/hooks/canvas/canvasPageHooks";
|
} from "@/hooks/canvas/canvasPageHooks";
|
||||||
import EditPageButtons from "./EditPageButtons";
|
import EditPageButtons from "./EditPageButtons";
|
||||||
|
import ClientOnly from "@/components/ClientOnly";
|
||||||
|
|
||||||
export default function EditPage({
|
export default function EditPage({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -73,11 +74,13 @@ export default function EditPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{settings.canvasId && (
|
{settings.canvasId && (
|
||||||
<EditPageButtons
|
<ClientOnly>
|
||||||
pageName={pageName}
|
<EditPageButtons
|
||||||
moduleName={moduleName}
|
pageName={pageName}
|
||||||
courseCanvasId={settings.canvasId}
|
moduleName={moduleName}
|
||||||
/>
|
courseCanvasId={settings.canvasId}
|
||||||
|
/>
|
||||||
|
</ClientOnly>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||||
import { Spinner } from "@/components/Spinner";
|
import { Spinner } from "@/components/Spinner";
|
||||||
import {
|
import {
|
||||||
useCanvasPagesQuery,
|
useCanvasPagesQuery,
|
||||||
@@ -5,7 +6,11 @@ import {
|
|||||||
useDeleteCanvasPageMutation,
|
useDeleteCanvasPageMutation,
|
||||||
useUpdateCanvasPageMutation,
|
useUpdateCanvasPageMutation,
|
||||||
} from "@/hooks/canvas/canvasPageHooks";
|
} from "@/hooks/canvas/canvasPageHooks";
|
||||||
|
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
|
||||||
import { usePageQuery } from "@/hooks/localCourse/pageHooks";
|
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";
|
import React from "react";
|
||||||
|
|
||||||
export default function EditPageButtons({
|
export default function EditPageButtons({
|
||||||
@@ -17,6 +22,8 @@ export default function EditPageButtons({
|
|||||||
moduleName: string;
|
moduleName: string;
|
||||||
courseCanvasId: number;
|
courseCanvasId: number;
|
||||||
}) {
|
}) {
|
||||||
|
const { courseName } = useCourseContext();
|
||||||
|
const { data: settings } = useLocalCourseSettingsQuery();
|
||||||
const { data: page } = usePageQuery(moduleName, pageName);
|
const { data: page } = usePageQuery(moduleName, pageName);
|
||||||
const { data: canvasPages } = useCanvasPagesQuery(courseCanvasId);
|
const { data: canvasPages } = useCanvasPagesQuery(courseCanvasId);
|
||||||
const createPageInCanvas = useCreateCanvasPageMutation(courseCanvasId);
|
const createPageInCanvas = useCreateCanvasPageMutation(courseCanvasId);
|
||||||
@@ -31,15 +38,8 @@ export default function EditPageButtons({
|
|||||||
deletePageInCanvas.isPending;
|
deletePageInCanvas.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-5 flex flex-row gap-x-3">
|
<div className="p-5 flex justify-end flex-row gap-x-3">
|
||||||
{pageInCanvas && (
|
{requestIsPending && <Spinner />}
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href={`https://snow.instructure.com/courses/${courseCanvasId}/pages/${pageInCanvas.page_id}`}
|
|
||||||
>
|
|
||||||
View Page In Canvas
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{!pageInCanvas && (
|
{!pageInCanvas && (
|
||||||
<button
|
<button
|
||||||
onClick={() => createPageInCanvas.mutate(page)}
|
onClick={() => createPageInCanvas.mutate(page)}
|
||||||
@@ -61,15 +61,27 @@ export default function EditPageButtons({
|
|||||||
Update in Canvas
|
Update in Canvas
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{pageInCanvas && (
|
||||||
|
<a
|
||||||
|
className="btn"
|
||||||
|
target="_blank"
|
||||||
|
href={`${baseCanvasUrl}/courses/${settings.canvasId}/pages/${pageInCanvas.url}`}
|
||||||
|
>
|
||||||
|
View in Canvas
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
{pageInCanvas && (
|
{pageInCanvas && (
|
||||||
<button
|
<button
|
||||||
|
className="btn-danger"
|
||||||
onClick={() => deletePageInCanvas.mutate(pageInCanvas.page_id)}
|
onClick={() => deletePageInCanvas.mutate(pageInCanvas.page_id)}
|
||||||
disabled={requestIsPending}
|
disabled={requestIsPending}
|
||||||
>
|
>
|
||||||
Delete from Canvas
|
Delete from Canvas
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{requestIsPending && <Spinner />}
|
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
|
||||||
|
Go Back
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,7 @@ import {
|
|||||||
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import QuizPreview from "./QuizPreview";
|
import QuizPreview from "./QuizPreview";
|
||||||
import {
|
import { QuizButtons } from "./QuizButton";
|
||||||
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";
|
|
||||||
|
|
||||||
const helpString = `QUESTION REFERENCE
|
const helpString = `QUESTION REFERENCE
|
||||||
---
|
---
|
||||||
@@ -60,6 +50,7 @@ this is a matching question
|
|||||||
^ left answer - right dropdown
|
^ left answer - right dropdown
|
||||||
^ other thing - another option`;
|
^ other thing - another option`;
|
||||||
|
|
||||||
|
|
||||||
export default function EditQuiz({
|
export default function EditQuiz({
|
||||||
moduleName,
|
moduleName,
|
||||||
quizName,
|
quizName,
|
||||||
@@ -125,66 +116,3 @@ export default function EditQuiz({
|
|||||||
</div>
|
</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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
|
|||||||
|
|
||||||
export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
|
export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ overflow: "scroll", height: "100%" }}>
|
<div style={{ overflow: "scroll", height: "100%" }}>
|
||||||
<div className="columns-2">
|
<div className="columns-2">
|
||||||
<div className="text-end">Name</div>
|
<div className="text-end">Name</div>
|
||||||
<div>{quiz.name}</div>
|
<div>{quiz.name}</div>
|
||||||
@@ -41,12 +41,14 @@ export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
|
|||||||
{quiz.description}
|
{quiz.description}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
{quiz.questions.map((question, i) => (
|
<div className="p-3 rounded-md bg-slate-950 m-5 flex flex-col gap-3">
|
||||||
<QuizQuestionPreview
|
{quiz.questions.map((question, i) => (
|
||||||
key={quizQuestionMarkdownUtils.toMarkdown(question)}
|
<QuizQuestionPreview
|
||||||
question={question}
|
key={quizQuestionMarkdownUtils.toMarkdown(question)}
|
||||||
/>
|
question={question}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@@ -62,10 +64,16 @@ export default function QuizPreview({ quiz }: { quiz: LocalQuiz }) {
|
|||||||
|
|
||||||
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded">
|
<div className="rounded bg-slate-900 px-2">
|
||||||
<div>Points: {question.points}</div>
|
<div className="flex flex-row justify-between text-slate-400">
|
||||||
<div>Type: {question.questionType}</div>
|
<div>{question.questionType}</div>
|
||||||
|
<div className="">
|
||||||
|
{question.points} {question.points === 1 ? " Point" : " Points"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
className="ms-4 mb-2"
|
||||||
dangerouslySetInnerHTML={{ __html: markdownToHTMLSafe(question.text) }}
|
dangerouslySetInnerHTML={{ __html: markdownToHTMLSafe(question.text) }}
|
||||||
></div>
|
></div>
|
||||||
{question.questionType === QuestionType.MATCHING && (
|
{question.questionType === QuestionType.MATCHING && (
|
||||||
@@ -73,10 +81,10 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
|||||||
{question.answers.map((answer) => (
|
{question.answers.map((answer) => (
|
||||||
<div
|
<div
|
||||||
key={JSON.stringify(answer)}
|
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="text-right my-auto">{answer.text} - </div>
|
||||||
<div className="col my-auto">{answer.matchedText}</div>
|
<div className="">{answer.matchedText}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{question.matchDistractors.map((distractor) => (
|
{question.matchDistractors.map((distractor) => (
|
||||||
@@ -94,32 +102,27 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
|
|||||||
{question.answers.map((answer) => (
|
{question.answers.map((answer) => (
|
||||||
<div
|
<div
|
||||||
key={JSON.stringify(answer)}
|
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 ? (
|
<div className="w-8 flex flex-col justify-center">
|
||||||
<svg
|
{answer.correct ? (
|
||||||
style={{ width: "1em" }}
|
<svg className="h-6" viewBox="0 0 24 24" fill="none">
|
||||||
className="me-1 my-auto"
|
<path
|
||||||
viewBox="0 0 24 24"
|
d="M4 12.6111L8.92308 17.5L20 6.5"
|
||||||
fill="none"
|
stroke="green"
|
||||||
>
|
strokeWidth="2"
|
||||||
<path
|
strokeLinecap="round"
|
||||||
d="M4 12.6111L8.92308 17.5L20 6.5"
|
strokeLinejoin="round"
|
||||||
stroke="green"
|
/>
|
||||||
strokeWidth="2"
|
</svg>
|
||||||
strokeLinecap="round"
|
) : question.questionType === QuestionType.MULTIPLE_ANSWERS ? (
|
||||||
strokeLinejoin="round"
|
<span className="mx-auto">{"[ ]"}</span>
|
||||||
/>
|
) : (
|
||||||
</svg>
|
<div></div>
|
||||||
) : (
|
)}
|
||||||
<div className="mr-1 my-auto" style={{ width: "1em" }}>
|
</div>
|
||||||
{question.questionType === QuestionType.MULTIPLE_ANSWERS && (
|
|
||||||
<span>[ ]</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className="markdownQuizAnswerPreview p-1"
|
className="markdownQuizAnswerPreview"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: markdownToHTMLSafe(answer.text),
|
__html: markdownToHTMLSafe(answer.text),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -119,4 +119,9 @@ select {
|
|||||||
|
|
||||||
.collapsible.expand {
|
.collapsible.expand {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.markdownQuizAnswerPreview p:last-child {
|
||||||
|
@apply p-0 m-0 ;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ export const SuspenseAndErrorHandling: FC<{ children: ReactNode }> = ({
|
|||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={reset}
|
onReset={reset}
|
||||||
fallbackRender={(props) => {
|
fallbackRender={(props) => {
|
||||||
toast.error(getErrorMessage(props.error))
|
toast.error(getErrorMessage(props.error));
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="p-3">{getErrorMessage(props.error)}</div>
|
<div className="p-3">{getErrorMessage(props.error)}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user