diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx index 9b2229e..3017c04 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx @@ -7,6 +7,17 @@ import { import { localAssignmentMarkdown } from "@/models/local/assignment/localAssignment"; import { useEffect, useState } from "react"; import AssignmentPreview from "./AssignmentPreview"; +import { getCourseUrl } from "@/services/urlUtils"; +import Link from "next/link"; +import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; +import { + useAddAssignmentToCanvasMutation, + useCanvasAssignmentsQuery, + useDeleteAssignmentFromCanvasMutation, +} from "@/hooks/canvas/canvasAssignmentHooks"; +import { Spinner } from "@/components/Spinner"; +import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils"; export default function EditAssignment({ moduleName, @@ -68,9 +79,71 @@ export default function EditAssignment({ -
- -
+ + + ); +} + +function AssignmentButtons({ + moduleName, + assignmentName, +}: { + assignmentName: string; + moduleName: string; +}) { + const { courseName } = useCourseContext(); + const { data: settings } = useLocalCourseSettingsQuery(); + const { data: canvasAssignments } = useCanvasAssignmentsQuery(); + const { data: assignment } = useAssignmentQuery(moduleName, assignmentName); + const addToCanvas = useAddAssignmentToCanvasMutation(); + const deleteFromCanvas = useDeleteAssignmentFromCanvasMutation(); + + const assignmentInCanvas = canvasAssignments.find( + (a) => a.name === assignmentName + ); + return ( +
+ {(addToCanvas.isPending || deleteFromCanvas.isPending) && } + {assignmentInCanvas && !assignmentInCanvas.published && ( +
Not Published
+ )} + {!assignmentInCanvas && ( + + )} + {assignmentInCanvas && ( + + View in Canvas + + )} + {assignmentInCanvas && ( + + )} + + Go Back +
); } diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx index 99db4fc..6df3cf3 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx @@ -135,10 +135,10 @@ function QuizButtons({ moduleName: string; toggleHelp: () => void; }) { + const { courseName } = useCourseContext(); + const { data: settings } = useLocalCourseSettingsQuery(); const { data: canvasQuizzes } = useCanvasQuizzesQuery(); const { data: quiz } = useQuizQuery(moduleName, quizName); - const { data: settings } = useLocalCourseSettingsQuery(); - const { courseName } = useCourseContext(); const addToCanvas = useAddQuizToCanvasMutation(); const deleteFromCanvas = useDeleteQuizFromCanvasMutation(); diff --git a/nextjs/src/components/editor/InnerMonacoEditor.tsx b/nextjs/src/components/editor/InnerMonacoEditor.tsx index f273a16..81c7ea1 100644 --- a/nextjs/src/components/editor/InnerMonacoEditor.tsx +++ b/nextjs/src/components/editor/InnerMonacoEditor.tsx @@ -46,14 +46,6 @@ export default function InnerMonacoEditor({ } }, [onChange, value]); - useEffect(() => { - window.addEventListener("resize", () => { - if (editorRef.current) { - editorRef.current.layout(); - } - }); - }, []); - return (
+ ["canvas", canvasCourseId, "assignments"] as const, + assignment: (canvasCourseId: number, assignmentName: string) => + ["canvas", canvasCourseId, "assignment", assignmentName] as const, +}; + +export const useCanvasAssignmentsQuery = () => { + const { data: settings } = useLocalCourseSettingsQuery(); + + return useSuspenseQuery({ + queryKey: canvasAssignmentKeys.assignments(settings.canvasId), + queryFn: async () => canvasAssignmentService.getAll(settings.canvasId), + }); +}; + +// export const useCanvasAssignmentsQuery = () => { +// const { data: settings } = useLocalCourseSettingsQuery(); +// const { data: allAssignments } = useInnerCanvasAssignmentsQuery(); + +// return useSuspenseQueries({ +// queries: allAssignments.map((a) => ({ +// queryKey: canvasAssignmentKeys.assignment(settings.canvasId, a.name), +// queryFn: () => a, +// })), +// combine: (results) => ({ +// data: results.map((r) => r.data), +// pending: results.some((r) => r.isPending), +// }), +// }); +// }; + +export const useAddAssignmentToCanvasMutation = () => { + const { data: settings } = useLocalCourseSettingsQuery(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (assignmnet: LocalAssignment) => { + const assignmentGroup = settings.assignmentGroups.find( + (g) => g.name === assignmnet.localAssignmentGroupName + ); + await canvasAssignmentService.create( + settings.canvasId, + assignmnet, + assignmentGroup?.canvasId + ); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: canvasAssignmentKeys.assignments(settings.canvasId), + }); + }, + }); +}; + +export const useDeleteAssignmentFromCanvasMutation = () => { + const { data: settings } = useLocalCourseSettingsQuery(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + canvasAssignmentId, + assignmentName, + }: { + canvasAssignmentId: number; + assignmentName: string; + }) => { + await canvasAssignmentService.delete( + settings.canvasId, + canvasAssignmentId, + assignmentName + ); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: canvasAssignmentKeys.assignments(settings.canvasId), + }); + }, + }); +}; diff --git a/nextjs/src/hooks/canvas/canvasQuizHooks.ts b/nextjs/src/hooks/canvas/canvasQuizHooks.ts index be71d95..5d527a3 100644 --- a/nextjs/src/hooks/canvas/canvasQuizHooks.ts +++ b/nextjs/src/hooks/canvas/canvasQuizHooks.ts @@ -8,7 +8,8 @@ import { canvasQuizService } from "@/services/canvas/canvasQuizService"; import { LocalQuiz } from "@/models/local/quiz/localQuiz"; export const canvasQuizKeys = { - quizzes: (canvasCourseId: number) => ["canvas", canvasCourseId, "quizzes"], + quizzes: (canvasCourseId: number) => + ["canvas", canvasCourseId, "quizzes"] as const, }; export const useCanvasQuizzesQuery = () => { @@ -29,16 +30,13 @@ export const useAddQuizToCanvasMutation = () => { const assignmentGroup = settings.assignmentGroups.find( (g) => g.name === quiz.localAssignmentGroupName ); - console.log("starting"); await canvasQuizService.create( settings.canvasId, quiz, assignmentGroup?.canvasId ); - console.log("ending"); }, onSuccess: () => { - console.log("invalidating"); queryClient.invalidateQueries({ queryKey: canvasQuizKeys.quizzes(settings.canvasId), }); diff --git a/nextjs/src/hooks/localCourse/pageHooks.ts b/nextjs/src/hooks/localCourse/pageHooks.ts index 805b2cc..3368180 100644 --- a/nextjs/src/hooks/localCourse/pageHooks.ts +++ b/nextjs/src/hooks/localCourse/pageHooks.ts @@ -6,7 +6,6 @@ import { useSuspenseQueries, useSuspenseQuery, } from "@tanstack/react-query"; -import axios from "axios"; import { localCourseKeys } from "./localCourseKeys"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; import { axiosClient } from "@/services/axiosUtils"; diff --git a/nextjs/src/services/canvas/canvasAssignmentService.ts b/nextjs/src/services/canvas/canvasAssignmentService.ts index 41a82ae..65ff0b0 100644 --- a/nextjs/src/services/canvas/canvasAssignmentService.ts +++ b/nextjs/src/services/canvas/canvasAssignmentService.ts @@ -6,6 +6,56 @@ import { markdownToHTMLSafe } from "../htmlMarkdownUtils"; import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse"; import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils"; +const createRubric = async ( + courseId: number, + assignmentCanvasId: number, + localAssignment: LocalAssignment +) => { + const criterion = localAssignment.rubric.map((rubricItem, i) => ({ + description: rubricItem.label, + points: rubricItem.points, + ratings: [ + { description: "Full Marks", points: rubricItem.points }, + { description: "No Marks", points: 0 }, + ], + })); + + const rubricBody = { + rubric_association_id: assignmentCanvasId, + rubric: { + title: `Rubric for Assignment: ${localAssignment.name}`, + association_id: assignmentCanvasId, + association_type: "Assignment", + use_for_grading: true, + criteria: criterion, + }, + rubric_association: { + association_id: assignmentCanvasId, + association_type: "Assignment", + purpose: "grading", + use_for_grading: true, + }, + }; + + const rubricUrl = `${canvasApi}/courses/${courseId}/rubrics`; + const rubricResponse = await axiosClient.post( + rubricUrl, + rubricBody + ); + + if (!rubricResponse.data) throw new Error("Failed to create rubric"); + + const assignmentPointAdjustmentUrl = `${canvasApi}/courses/${courseId}/assignments/${assignmentCanvasId}`; + const assignmentPointAdjustmentBody = { + assignment: { points_possible: assignmentPoints(localAssignment) }, + }; + + await axiosClient.put( + assignmentPointAdjustmentUrl, + assignmentPointAdjustmentBody + ); +}; + export const canvasAssignmentService = { async getAll(courseId: number): Promise { const url = `${canvasApi}/courses/${courseId}/assignments`; @@ -23,24 +73,22 @@ export const canvasAssignmentService = { canvasCourseId: number, localAssignment: LocalAssignment, canvasAssignmentGroupId?: number - ): Promise { + ) { console.log(`Creating assignment: ${localAssignment.name}`); const url = `${canvasApi}/courses/${canvasCourseId}/assignments`; const body = { - assignment: { - name: localAssignment.name, - submission_types: localAssignment.submissionTypes.map((t) => - t.toString() - ), - allowed_extensions: localAssignment.allowedFileUploadExtensions.map( - (e) => e.toString() - ), - description: markdownToHTMLSafe(localAssignment.description), - due_at: localAssignment.dueAt, - lock_at: localAssignment.lockAt, - points_possible: assignmentPoints(localAssignment), - assignment_group_id: canvasAssignmentGroupId, - }, + name: localAssignment.name, + submission_types: localAssignment.submissionTypes.map((t) => + t.toString() + ), + allowed_extensions: localAssignment.allowedFileUploadExtensions.map((e) => + e.toString() + ), + description: markdownToHTMLSafe(localAssignment.description), + due_at: localAssignment.dueAt, + lock_at: localAssignment.lockAt, + points_possible: assignmentPoints(localAssignment), + assignment_group_id: canvasAssignmentGroupId, }; const response = await axiosClient.post(url, body); @@ -48,11 +96,7 @@ export const canvasAssignmentService = { if (!canvasAssignment) throw new Error("Created Canvas assignment is null"); - await this.createRubric( - canvasCourseId, - canvasAssignment.id, - localAssignment - ); + await createRubric(canvasCourseId, canvasAssignment.id, localAssignment); return canvasAssignment.id; }, @@ -62,28 +106,26 @@ export const canvasAssignmentService = { canvasAssignmentId: number, localAssignment: LocalAssignment, canvasAssignmentGroupId?: number - ): Promise { + ) { console.log(`Updating assignment: ${localAssignment.name}`); const url = `${canvasApi}/courses/${courseId}/assignments/${canvasAssignmentId}`; const body = { - assignment: { - name: localAssignment.name, - submission_types: localAssignment.submissionTypes.map((t) => - t.toString() - ), - allowed_extensions: localAssignment.allowedFileUploadExtensions.map( - (e) => e.toString() - ), - description: markdownToHTMLSafe(localAssignment.description), - due_at: localAssignment.dueAt, - lock_at: localAssignment.lockAt, - points_possible: assignmentPoints(localAssignment), - assignment_group_id: canvasAssignmentGroupId, - }, + name: localAssignment.name, + submission_types: localAssignment.submissionTypes.map((t) => + t.toString() + ), + allowed_extensions: localAssignment.allowedFileUploadExtensions.map((e) => + e.toString() + ), + description: markdownToHTMLSafe(localAssignment.description), + due_at: localAssignment.dueAt, + lock_at: localAssignment.lockAt, + points_possible: assignmentPoints(localAssignment), + assignment_group_id: canvasAssignmentGroupId, }; await axiosClient.put(url, body); - await this.createRubric(courseId, canvasAssignmentId, localAssignment); + await createRubric(courseId, canvasAssignmentId, localAssignment); }, async delete( @@ -100,54 +142,4 @@ export const canvasAssignmentService = { throw new Error("Failed to delete assignment"); } }, - - async createRubric( - courseId: number, - assignmentCanvasId: number, - localAssignment: LocalAssignment - ): Promise { - const criterion = localAssignment.rubric.map((rubricItem, i) => ({ - description: rubricItem.label, - points: rubricItem.points, - ratings: [ - { description: "Full Marks", points: rubricItem.points }, - { description: "No Marks", points: 0 }, - ], - })); - - const rubricBody = { - rubric_association_id: assignmentCanvasId, - rubric: { - title: `Rubric for Assignment: ${localAssignment.name}`, - association_id: assignmentCanvasId, - association_type: "Assignment", - use_for_grading: true, - criteria: criterion, - }, - rubric_association: { - association_id: assignmentCanvasId, - association_type: "Assignment", - purpose: "grading", - use_for_grading: true, - }, - }; - - const rubricUrl = `${canvasApi}/courses/${courseId}/rubrics`; - const rubricResponse = await axiosClient.post( - rubricUrl, - rubricBody - ); - - if (!rubricResponse.data) throw new Error("Failed to create rubric"); - - const assignmentPointAdjustmentUrl = `${canvasApi}/courses/${courseId}/assignments/${assignmentCanvasId}`; - const assignmentPointAdjustmentBody = { - assignment: { points_possible: assignmentPoints(localAssignment) }, - }; - - await axiosClient.put( - assignmentPointAdjustmentUrl, - assignmentPointAdjustmentBody - ); - }, };