@@ -94,12 +108,30 @@ export default function EditAssignment({
);
}
+function getHelpString(settings: LocalCourseSettings) {
+ const groupNames = settings.assignmentGroups.map((g) => g.name).join("\n- ");
+ const helpString = `SubmissionTypes:
+- ${AssignmentSubmissionType.ONLINE_TEXT_ENTRY}
+- ${AssignmentSubmissionType.ONLINE_UPLOAD}
+- ${AssignmentSubmissionType.DISCUSSION_TOPIC}
+AllowedFileUploadExtensions:
+- pdf
+- jpg
+- jpeg
+- png
+Assignment Group Names:
+- ${groupNames}`;
+ return helpString;
+}
+
function AssignmentButtons({
moduleName,
assignmentName,
+ toggleHelp,
}: {
assignmentName: string;
moduleName: string;
+ toggleHelp: () => void;
}) {
const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery();
@@ -113,61 +145,66 @@ function AssignmentButtons({
(a) => a.name === assignmentName
);
return (
-
- {(addToCanvas.isPending ||
- deleteFromCanvas.isPending ||
- updateAssignment.isPending) &&
}
- {assignmentInCanvas && !assignmentInCanvas.published && (
-
Not Published
- )}
- {!assignmentInCanvas && (
-
- )}
- {assignmentInCanvas && (
-
- View in Canvas
-
- )}
- {assignmentInCanvas && (
-
- )}
- {assignmentInCanvas && (
-
- )}
-
- Go Back
-
+
+
+
+
+
+ {(addToCanvas.isPending ||
+ deleteFromCanvas.isPending ||
+ updateAssignment.isPending) &&
}
+ {assignmentInCanvas && !assignmentInCanvas.published && (
+
Not Published
+ )}
+ {!assignmentInCanvas && (
+
+ )}
+ {assignmentInCanvas && (
+
+ View in Canvas
+
+ )}
+ {assignmentInCanvas && (
+
+ )}
+ {assignmentInCanvas && (
+
+ )}
+
+ Go Back
+
+
);
}
diff --git a/nextjs/src/app/globals.css b/nextjs/src/app/globals.css
index d390c8b..f034d98 100644
--- a/nextjs/src/app/globals.css
+++ b/nextjs/src/app/globals.css
@@ -23,6 +23,10 @@
}
.monaco-editor { position: absolute !important; }
+.monaco-editor .mtk1 {
+ @apply text-slate-300;
+}
+
h1 {
@apply text-4xl font-bold my-1;
}
diff --git a/nextjs/src/app/layout.tsx b/nextjs/src/app/layout.tsx
index 1f4fd61..bcc07e4 100644
--- a/nextjs/src/app/layout.tsx
+++ b/nextjs/src/app/layout.tsx
@@ -6,7 +6,6 @@ import { getQueryClient } from "./providersQueryClientUtils";
import { hydrateCourses } from "@/hooks/hookHydration";
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
import { MyToaster } from "./MyToaster";
-import "katex/dist/katex.min.css";
export const metadata: Metadata = {
title: "Canvas Manager 2.0",
diff --git a/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts b/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts
index 7f4d930..e4ccaac 100644
--- a/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts
+++ b/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts
@@ -12,8 +12,8 @@ import { LocalAssignment } from "@/models/local/assignment/localAssignment";
export const canvasAssignmentKeys = {
assignments: (canvasCourseId: number) =>
["canvas", canvasCourseId, "assignments"] as const,
- assignment: (canvasCourseId: number, assignmentName: string) =>
- ["canvas", canvasCourseId, "assignment", assignmentName] as const,
+ // assignment: (canvasCourseId: number, assignmentName: string) =>
+ // ["canvas", canvasCourseId, "assignment", assignmentName] as const,
};
export const useCanvasAssignmentsQuery = () => {
diff --git a/nextjs/src/hooks/canvas/canvasHooks.ts b/nextjs/src/hooks/canvas/canvasHooks.ts
index 929d45e..3261540 100644
--- a/nextjs/src/hooks/canvas/canvasHooks.ts
+++ b/nextjs/src/hooks/canvas/canvasHooks.ts
@@ -1,6 +1,5 @@
import { canvasService } from "@/services/canvas/canvasService";
-import { QueryClient, useSuspenseQuery } from "@tanstack/react-query";
-import { canvasCourseModuleKeys } from "./canvasModuleHooks";
+import { useSuspenseQuery } from "@tanstack/react-query";
export const canvasKeys = {
allTerms: ["all canvas terms"] as const,
diff --git a/nextjs/src/hooks/hookHydration.ts b/nextjs/src/hooks/hookHydration.ts
index d09b76f..2586fd2 100644
--- a/nextjs/src/hooks/hookHydration.ts
+++ b/nextjs/src/hooks/hookHydration.ts
@@ -2,10 +2,18 @@ import { QueryClient } from "@tanstack/react-query";
import { localCourseKeys } from "./localCourse/localCourseKeys";
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import { LocalCourseSettings } from "@/models/local/localCourse";
+import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService";
+import { canvasAssignmentKeys } from "./canvas/canvasAssignmentHooks";
+import { LocalAssignment } from "@/models/local/assignment/localAssignment";
+import { LocalCoursePage } from "@/models/local/page/localCoursePage";
+import { LocalQuiz } from "@/models/local/quiz/localQuiz";
+import { canvasQuizService } from "@/services/canvas/canvasQuizService";
+import { canvasPageService } from "@/services/canvas/canvasPageService";
+import { canvasQuizKeys } from "./canvas/canvasQuizHooks";
+import { canvasPageKeys } from "./canvas/canvasPageHooks";
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr
export const hydrateCourses = async (queryClient: QueryClient) => {
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
- const courseNames = allSettings.map((s) => s.name);
await queryClient.prefetchQuery({
queryKey: localCourseKeys.allCoursesSettings,
queryFn: () => allSettings,
@@ -27,51 +35,7 @@ export const hydrateCourse = async (
courseName
);
const modulesData = await Promise.all(
- moduleNames.map(async (moduleName) => {
- const [assignmentNames, pageNames, quizNames] = await Promise.all([
- await fileStorageService.assignments.getAssignmentNames(
- courseName,
- moduleName
- ),
- await fileStorageService.pages.getPageNames(courseName, moduleName),
- await fileStorageService.quizzes.getQuizNames(courseName, moduleName),
- ]);
-
- const [assignments, quizzes, pages] = await Promise.all([
- await Promise.all(
- assignmentNames.map(
- async (assignmentName) =>
- await fileStorageService.assignments.getAssignment(
- courseName,
- moduleName,
- assignmentName
- )
- )
- ),
- await Promise.all(
- quizNames.map(
- async (quizName) =>
- await fileStorageService.quizzes.getQuiz(courseName, moduleName, quizName)
- )
- ),
- await Promise.all(
- pageNames.map(
- async (pageName) =>
- await fileStorageService.pages.getPage(courseName, moduleName, pageName)
- )
- ),
- ]);
-
- return {
- moduleName,
- assignmentNames,
- pageNames,
- quizNames,
- assignments,
- quizzes,
- pages,
- };
- })
+ moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName))
);
await queryClient.prefetchQuery({
@@ -84,68 +48,146 @@ export const hydrateCourse = async (
});
await Promise.all(
- modulesData.map(
- async ({
- moduleName,
- assignmentNames,
- pageNames,
- quizNames,
- assignments,
- quizzes,
- pages,
- }) => {
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
- queryFn: () => assignmentNames,
- });
- await Promise.all(
- assignments.map(
- async (assignment) =>
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.assignment(
- courseName,
- moduleName,
- assignment.name
- ),
- queryFn: () => assignment,
- })
+ modulesData.map((d) => hydrateModuleData(d, courseName, queryClient))
+ );
+};
+
+export const hydrateCanvasCourse = async (
+ canvasCourseId: number,
+ queryClient: QueryClient
+) => {
+ await Promise.all([
+ queryClient.prefetchQuery({
+ queryKey: canvasAssignmentKeys.assignments(canvasCourseId),
+ queryFn: async () => await canvasAssignmentService.getAll(canvasCourseId),
+ }),
+ queryClient.prefetchQuery({
+ queryKey: canvasQuizKeys.quizzes(canvasCourseId),
+ queryFn: async () => await canvasQuizService.getAll(canvasCourseId),
+ }),
+ queryClient.prefetchQuery({
+ queryKey: canvasPageKeys.pagesInCourse(canvasCourseId),
+ queryFn: async () => await canvasPageService.getAll(canvasCourseId),
+ }),
+ ]);
+};
+
+const loadAllModuleData = async (courseName: string, moduleName: string) => {
+ const [assignmentNames, pageNames, quizNames] = await Promise.all([
+ await fileStorageService.assignments.getAssignmentNames(
+ courseName,
+ moduleName
+ ),
+ await fileStorageService.pages.getPageNames(courseName, moduleName),
+ await fileStorageService.quizzes.getQuizNames(courseName, moduleName),
+ ]);
+
+ const [assignments, quizzes, pages] = await Promise.all([
+ await Promise.all(
+ assignmentNames.map(
+ async (assignmentName) =>
+ await fileStorageService.assignments.getAssignment(
+ courseName,
+ moduleName,
+ assignmentName
)
- );
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.quizNames(courseName, moduleName),
- queryFn: () => quizNames,
- });
- await Promise.all(
- quizzes.map(
- async (quiz) =>
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.quiz(
- courseName,
- moduleName,
- quiz.name
- ),
- queryFn: () => quiz,
- })
+ )
+ ),
+ await Promise.all(
+ quizNames.map(
+ async (quizName) =>
+ await fileStorageService.quizzes.getQuiz(
+ courseName,
+ moduleName,
+ quizName
)
- );
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.pageNames(courseName, moduleName),
- queryFn: () => pageNames,
- });
- await Promise.all(
- pages.map(
- async (page) =>
- await queryClient.prefetchQuery({
- queryKey: localCourseKeys.page(
- courseName,
- moduleName,
- page.name
- ),
- queryFn: () => page,
- })
+ )
+ ),
+ await Promise.all(
+ pageNames.map(
+ async (pageName) =>
+ await fileStorageService.pages.getPage(
+ courseName,
+ moduleName,
+ pageName
)
- );
- }
+ )
+ ),
+ ]);
+
+ return {
+ moduleName,
+ assignmentNames,
+ pageNames,
+ quizNames,
+ assignments,
+ quizzes,
+ pages,
+ };
+};
+
+const hydrateModuleData = async (
+ {
+ moduleName,
+ assignmentNames,
+ pageNames,
+ quizNames,
+ assignments,
+ quizzes,
+ pages,
+ }: {
+ moduleName: string;
+ assignmentNames: string[];
+ pageNames: string[];
+ quizNames: string[];
+ assignments: LocalAssignment[];
+ quizzes: LocalQuiz[];
+ pages: LocalCoursePage[];
+ },
+ courseName: string,
+ queryClient: QueryClient
+) => {
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
+ queryFn: () => assignmentNames,
+ });
+ await Promise.all(
+ assignments.map(
+ async (assignment) =>
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.assignment(
+ courseName,
+ moduleName,
+ assignment.name
+ ),
+ queryFn: () => assignment,
+ })
+ )
+ );
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.quizNames(courseName, moduleName),
+ queryFn: () => quizNames,
+ });
+ await Promise.all(
+ quizzes.map(
+ async (quiz) =>
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.quiz(courseName, moduleName, quiz.name),
+ queryFn: () => quiz,
+ })
+ )
+ );
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.pageNames(courseName, moduleName),
+ queryFn: () => pageNames,
+ });
+ await Promise.all(
+ pages.map(
+ async (page) =>
+ await queryClient.prefetchQuery({
+ queryKey: localCourseKeys.page(courseName, moduleName, page.name),
+ queryFn: () => page,
+ })
)
);
};
diff --git a/nextjs/src/models/local/timeUtils.ts b/nextjs/src/models/local/timeUtils.ts
index cab4849..0c51d8b 100644
--- a/nextjs/src/models/local/timeUtils.ts
+++ b/nextjs/src/models/local/timeUtils.ts
@@ -37,7 +37,6 @@ const _getDateFromISO = (value: string): Date | undefined => {
};
export const getDateFromString = (value: string): Date | undefined => {
-
const ampmDateRegex =
/^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}\s{1}[APap][Mm]$/; //"M/D/YYYY h:mm:ss AM/PM"
const militaryDateRegex = /^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{2}:\d{2}$/; //"MM/DD/YYYY HH:mm:ss"
@@ -52,7 +51,7 @@ export const getDateFromString = (value: string): Date | undefined => {
const [datePart, timePart] = value.split(" ");
return _getDateFromMilitary(datePart, timePart);
} else {
- console.log("invalid date format", value);
+ if (value) console.log("invalid date format", value);
return undefined;
}
};
@@ -93,7 +92,6 @@ export const dateToMarkdownString = (date: Date) => {
return `${stringMonth}/${stringDay}/${stringYear} ${stringHours}:${stringMinutes}:${stringSeconds}`;
};
-
export const getDateOnlyMarkdownString = (date: Date) => {
- return dateToMarkdownString(date).split(" ")[0]
-}
\ No newline at end of file
+ return dateToMarkdownString(date).split(" ")[0];
+};
diff --git a/nextjs/src/services/canvas/canvasAssignmentGroupService.ts b/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
index 2237bc7..562315e 100644
--- a/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
+++ b/nextjs/src/services/canvas/canvasAssignmentGroupService.ts
@@ -1,4 +1,4 @@
-import { canvasApi, canvasServiceUtils } from "./canvasServiceUtils";
+import { canvasApi, paginatedRequest } from "./canvasServiceUtils";
import { axiosClient } from "../axiosUtils";
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
@@ -8,7 +8,7 @@ export const canvasAssignmentGroupService = {
async getAll(courseId: number): Promise
{
console.log("Requesting assignment groups");
const url = `${canvasApi}/courses/${courseId}/assignment_groups`;
- const assignmentGroups = await canvasServiceUtils.paginatedRequest<
+ const assignmentGroups = await paginatedRequest<
CanvasAssignmentGroup[]
>({
url,
diff --git a/nextjs/src/services/canvas/canvasAssignmentService.ts b/nextjs/src/services/canvas/canvasAssignmentService.ts
index 270e664..061718a 100644
--- a/nextjs/src/services/canvas/canvasAssignmentService.ts
+++ b/nextjs/src/services/canvas/canvasAssignmentService.ts
@@ -1,75 +1,12 @@
import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment";
-import {
- canvasApi,
- canvasServiceUtils,
- paginatedRequest,
-} from "./canvasServiceUtils";
+import { canvasApi, paginatedRequest } from "./canvasServiceUtils";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { axiosClient } from "../axiosUtils";
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse";
import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils";
-import {
- getDateFromString,
- getDateFromStringOrThrow,
-} from "@/models/local/timeUtils";
+import { getDateFromString } from "@/models/local/timeUtils";
-const createRubric = async (
- courseId: number,
- assignmentCanvasId: number,
- localAssignment: LocalAssignment
-) => {
- const criterion = localAssignment.rubric
- .map((rubricItem) => ({
- description: rubricItem.label,
- points: rubricItem.points,
- ratings: {
- 0: { description: "Full Marks", points: rubricItem.points },
- 1: { description: "No Marks", points: 0 },
- },
- }))
- .reduce((acc, item, index) => {
- return {
- ...acc,
- [index]: item,
- };
- }, {} as { [key: number]: { description: string; points: number; ratings: { [key: number]: { description: string; points: number } } } });
-
- 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 {
@@ -163,3 +100,60 @@ export const canvasAssignmentService = {
}
},
};
+
+const createRubric = async (
+ courseId: number,
+ assignmentCanvasId: number,
+ localAssignment: LocalAssignment
+) => {
+ const criterion = localAssignment.rubric
+ .map((rubricItem) => ({
+ description: rubricItem.label,
+ points: rubricItem.points,
+ ratings: {
+ 0: { description: "Full Marks", points: rubricItem.points },
+ 1: { description: "No Marks", points: 0 },
+ },
+ }))
+ .reduce((acc, item, index) => {
+ return {
+ ...acc,
+ [index]: item,
+ };
+ }, {} as { [key: number]: { description: string; points: number; ratings: { [key: number]: { description: string; points: number } } } });
+
+ 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
+ );
+};
diff --git a/nextjs/src/services/canvas/canvasServiceUtils.ts b/nextjs/src/services/canvas/canvasServiceUtils.ts
index 49cf90e..6845126 100644
--- a/nextjs/src/services/canvas/canvasServiceUtils.ts
+++ b/nextjs/src/services/canvas/canvasServiceUtils.ts
@@ -14,18 +14,26 @@ const getNextUrl = (
? (headers.get("link") as string)
: ((headers as RawAxiosResponseHeaders)["link"] as string);
- if (!linkHeader) return undefined;
+ if (!linkHeader) {
+ console.log("could not find link header in the response");
+ return undefined;
+ }
const links = linkHeader.split(",").map((link) => link.trim());
const nextLink = links.find((link) => link.includes('rel="next"'));
- if (!nextLink) return undefined;
+ if (!nextLink) {
+ // console.log(
+ // "could not find next url in link header, reached end of pagination"
+ // );
+ return undefined;
+ }
const nextUrl = nextLink.split(";")[0].trim().slice(1, -1);
return nextUrl;
};
-export async function paginatedRequest(request: {
+export async function paginatedRequest(request: {
url: string;
}): Promise {
var requestCount = 1;
@@ -36,20 +44,19 @@ export async function paginatedRequest(request: {
url.toString()
);
- if (!Array.isArray(firstData)) {
- return firstData;
- }
+ // if (!Array.isArray(firstData)) {
+ // return firstData;
+ // }
-
- var returnData = firstData ? [firstData] : [];
+ var returnData = [...firstData];
var nextUrl = getNextUrl(firstHeaders);
- console.log("got first request", nextUrl, firstHeaders);
+ // console.log("got first request", nextUrl, firstHeaders);
while (nextUrl) {
requestCount += 1;
const { data, headers } = await axiosClient.get(nextUrl);
if (data) {
- returnData = [...returnData, data];
+ returnData = returnData.concat(data);
}
nextUrl = getNextUrl(headers);
}
@@ -60,5 +67,5 @@ export async function paginatedRequest(request: {
);
}
- return returnData;
+ return returnData as T;
}