mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 23:58:31 -06:00
refactoring files to be located by feature
This commit is contained in:
214
src/features/local/assignments/assignmentHooks.ts
Normal file
214
src/features/local/assignments/assignmentHooks.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
"use client";
|
||||
import { useTRPC } from "@/services/serverFunctions/trpcClient";
|
||||
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||
import {
|
||||
extractImageSources,
|
||||
markdownToHtmlNoImages,
|
||||
} from "@/services/htmlMarkdownUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
useSuspenseQuery,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
useLocalCourseSettingsQuery,
|
||||
useUpdateLocalCourseSettingsMutation,
|
||||
} from "@/hooks/localCourse/localCoursesHooks";
|
||||
|
||||
export const useAssignmentQuery = (
|
||||
moduleName: string,
|
||||
assignmentName: string
|
||||
) => {
|
||||
const { courseName } = useCourseContext();
|
||||
const trpc = useTRPC();
|
||||
return useSuspenseQuery(
|
||||
trpc.assignment.getAssignment.queryOptions({
|
||||
moduleName,
|
||||
courseName,
|
||||
assignmentName,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const enable_images = process.env.NEXT_PUBLIC_ENABLE_FILE_SYNC === "true";
|
||||
|
||||
export const useUpdateImageSettingsForAssignment = ({
|
||||
moduleName,
|
||||
assignmentName,
|
||||
}: {
|
||||
moduleName: string;
|
||||
assignmentName: string;
|
||||
}) => {
|
||||
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const addNewImagesToCanvasMutation = useAddNewImagesToCanvasMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!enable_images) {
|
||||
console.log(
|
||||
"not uploading images, NEXT_PUBLIC_ENABLE_FILE_SYNC is not set to true"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
console.log("not updating image assets, still loading");
|
||||
return;
|
||||
}
|
||||
setIsPending(true);
|
||||
const assignmentMarkdown = markdownToHtmlNoImages(assignment.description);
|
||||
|
||||
addNewImagesToCanvasMutation
|
||||
.mutateAsync({
|
||||
markdownString: assignmentMarkdown,
|
||||
})
|
||||
.then(() => setIsPending(false));
|
||||
// not sure why mutation reference changes...
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assignment.description, isPending]);
|
||||
return { isPending };
|
||||
};
|
||||
|
||||
export const useAddNewImagesToCanvasMutation = () => {
|
||||
const { data: settings } = useLocalCourseSettingsQuery();
|
||||
const trpc = useTRPC();
|
||||
const createCanvasUrlMutation = useMutation(
|
||||
trpc.canvasFile.getCanvasFileUrl.mutationOptions()
|
||||
);
|
||||
const updateSettings = useUpdateLocalCourseSettingsMutation();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ markdownString }: { markdownString: string }) => {
|
||||
const imageSources = extractImageSources(markdownString);
|
||||
// console.log("original image urls", imageSources);
|
||||
const newImages = imageSources.filter((source) =>
|
||||
settings.assets.every((a) => a.sourceUrl !== source)
|
||||
);
|
||||
|
||||
if (newImages.length === 0) {
|
||||
console.log("no new images to upload");
|
||||
return;
|
||||
}
|
||||
|
||||
const newAssets = await Promise.all(
|
||||
newImages.map(async (source) => {
|
||||
console.log("uploading image to canvas", source);
|
||||
const canvasUrl = await createCanvasUrlMutation.mutateAsync({
|
||||
sourceUrl: source,
|
||||
canvasCourseId: settings.canvasId,
|
||||
});
|
||||
console.log("got canvas url", source, canvasUrl);
|
||||
return { sourceUrl: source, canvasUrl };
|
||||
})
|
||||
);
|
||||
await updateSettings.mutateAsync({
|
||||
settings: {
|
||||
...settings,
|
||||
assets: [...settings.assets, ...newAssets],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAssignmentNamesQuery = (moduleName: string) => {
|
||||
const { courseName } = useCourseContext();
|
||||
const trpc = useTRPC();
|
||||
return useSuspenseQuery({
|
||||
...trpc.assignment.getAllAssignments.queryOptions({
|
||||
moduleName,
|
||||
courseName,
|
||||
}),
|
||||
select: (assignments) => assignments.map((a) => a.name),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAssignmentMutation = () => {
|
||||
const trpc = useTRPC();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
trpc.assignment.updateAssignment.mutationOptions({
|
||||
onSuccess: (
|
||||
_,
|
||||
{
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
previousAssignmentName,
|
||||
previousModuleName,
|
||||
}
|
||||
) => {
|
||||
if (moduleName !== previousModuleName) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAllAssignments.queryKey({
|
||||
courseName,
|
||||
moduleName: previousModuleName,
|
||||
}),
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAllAssignments.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
}),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAssignment.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
}),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAssignment.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName: previousAssignmentName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const useCreateAssignmentMutation = () => {
|
||||
const trpc = useTRPC();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
trpc.assignment.createAssignment.mutationOptions({
|
||||
onSuccess: (_result, { courseName, moduleName }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAllAssignments.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const useDeleteAssignmentMutation = () => {
|
||||
const trpc = useTRPC();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
trpc.assignment.deleteAssignment.mutationOptions({
|
||||
onSuccess: (_result, { courseName, moduleName, assignmentName }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAllAssignments.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
}),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.assignment.getAssignment.queryKey({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
}),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
116
src/features/local/assignments/assignmentRouter.ts
Normal file
116
src/features/local/assignments/assignmentRouter.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import publicProcedure from "../../../services/serverFunctions/procedures/public";
|
||||
import { z } from "zod";
|
||||
import { router } from "../../../services/serverFunctions/trpcSetup";
|
||||
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
|
||||
import { zodLocalAssignment } from "@/features/local/assignments/models/localAssignment";
|
||||
|
||||
export const assignmentRouter = router({
|
||||
getAssignment: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
assignmentName: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { courseName, moduleName, assignmentName } }) => {
|
||||
const assignment = await fileStorageService.assignments.getAssignment(
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName
|
||||
);
|
||||
// console.log(assignment);
|
||||
return assignment;
|
||||
}),
|
||||
getAllAssignments: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { courseName, moduleName } }) => {
|
||||
const assignments = await fileStorageService.assignments.getAssignments(
|
||||
courseName,
|
||||
moduleName
|
||||
);
|
||||
return assignments;
|
||||
}),
|
||||
createAssignment: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
assignmentName: z.string(),
|
||||
assignment: zodLocalAssignment,
|
||||
})
|
||||
)
|
||||
.mutation(
|
||||
async ({
|
||||
input: { courseName, moduleName, assignmentName, assignment },
|
||||
}) => {
|
||||
await fileStorageService.assignments.updateOrCreateAssignment({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
assignment,
|
||||
});
|
||||
}
|
||||
),
|
||||
updateAssignment: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
previousModuleName: z.string(),
|
||||
previousAssignmentName: z.string(),
|
||||
assignmentName: z.string(),
|
||||
assignment: zodLocalAssignment,
|
||||
})
|
||||
)
|
||||
.mutation(
|
||||
async ({
|
||||
input: {
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
assignment,
|
||||
previousModuleName,
|
||||
previousAssignmentName,
|
||||
},
|
||||
}) => {
|
||||
await fileStorageService.assignments.updateOrCreateAssignment({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
assignment,
|
||||
});
|
||||
|
||||
if (
|
||||
assignmentName !== previousAssignmentName ||
|
||||
moduleName !== previousModuleName
|
||||
) {
|
||||
await fileStorageService.assignments.delete({
|
||||
courseName,
|
||||
moduleName: previousModuleName,
|
||||
assignmentName: previousAssignmentName,
|
||||
});
|
||||
}
|
||||
}
|
||||
),
|
||||
deleteAssignment: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
assignmentName: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input: { courseName, moduleName, assignmentName } }) => {
|
||||
await fileStorageService.assignments.delete({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
export enum AssignmentSubmissionType {
|
||||
ONLINE_TEXT_ENTRY = "online_text_entry",
|
||||
ONLINE_UPLOAD = "online_upload",
|
||||
ONLINE_QUIZ = "online_quiz",
|
||||
DISCUSSION_TOPIC = "discussion_topic",
|
||||
ONLINE_URL = "online_url",
|
||||
NONE = "none",
|
||||
}
|
||||
export const zodAssignmentSubmissionType = z.enum([
|
||||
AssignmentSubmissionType.ONLINE_TEXT_ENTRY,
|
||||
AssignmentSubmissionType.ONLINE_UPLOAD,
|
||||
AssignmentSubmissionType.ONLINE_QUIZ,
|
||||
AssignmentSubmissionType.DISCUSSION_TOPIC,
|
||||
AssignmentSubmissionType.ONLINE_URL,
|
||||
AssignmentSubmissionType.NONE,
|
||||
]);
|
||||
|
||||
export const AssignmentSubmissionTypeList: AssignmentSubmissionType[] = [
|
||||
AssignmentSubmissionType.ONLINE_TEXT_ENTRY,
|
||||
AssignmentSubmissionType.ONLINE_UPLOAD,
|
||||
AssignmentSubmissionType.ONLINE_QUIZ,
|
||||
AssignmentSubmissionType.DISCUSSION_TOPIC,
|
||||
AssignmentSubmissionType.ONLINE_URL,
|
||||
AssignmentSubmissionType.NONE,
|
||||
] as const;
|
||||
40
src/features/local/assignments/models/localAssignment.ts
Normal file
40
src/features/local/assignments/models/localAssignment.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IModuleItem } from "../../../../models/local/IModuleItem";
|
||||
import {
|
||||
AssignmentSubmissionType,
|
||||
zodAssignmentSubmissionType,
|
||||
} from "./assignmentSubmissionType";
|
||||
import { RubricItem, zodRubricItem } from "./rubricItem";
|
||||
import { assignmentMarkdownParser } from "./utils/assignmentMarkdownParser";
|
||||
import { assignmentMarkdownSerializer } from "./utils/assignmentMarkdownSerializer";
|
||||
import { z } from "zod";
|
||||
|
||||
export interface LocalAssignment extends IModuleItem {
|
||||
name: string;
|
||||
description: string;
|
||||
lockAt?: string; // 08/21/2023 23:59:00
|
||||
dueAt: string; // 08/21/2023 23:59:00
|
||||
localAssignmentGroupName?: string;
|
||||
submissionTypes: AssignmentSubmissionType[];
|
||||
allowedFileUploadExtensions: string[];
|
||||
rubric: RubricItem[];
|
||||
githubClassroomAssignmentShareLink?: string;
|
||||
githubClassroomAssignmentLink?: string;
|
||||
}
|
||||
|
||||
export const zodLocalAssignment = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
lockAt: z.string().optional(),
|
||||
dueAt: z.string(),
|
||||
localAssignmentGroupName: z.string().optional(),
|
||||
submissionTypes: zodAssignmentSubmissionType.array(),
|
||||
allowedFileUploadExtensions: z.string().array(),
|
||||
rubric: zodRubricItem.array(),
|
||||
githubClassroomAssignmentShareLink: z.string().optional(),
|
||||
githubClassroomAssignmentLink: z.string().optional(),
|
||||
});
|
||||
|
||||
export const localAssignmentMarkdown = {
|
||||
parseMarkdown: assignmentMarkdownParser.parseMarkdown,
|
||||
toMarkdown: assignmentMarkdownSerializer.toMarkdown,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export interface LocalAssignmentGroup {
|
||||
canvasId?: number;
|
||||
id: string;
|
||||
name: string;
|
||||
weight: number;
|
||||
}
|
||||
export const zodLocalAssignmentGroup = z.object({
|
||||
canvasId: z.optional(z.number()),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
weight: z.number(),
|
||||
});
|
||||
16
src/features/local/assignments/models/rubricItem.ts
Normal file
16
src/features/local/assignments/models/rubricItem.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export interface RubricItem {
|
||||
label: string;
|
||||
points: number;
|
||||
}
|
||||
|
||||
export const zodRubricItem = z.object({
|
||||
label: z.string(),
|
||||
points: z.number(),
|
||||
});
|
||||
|
||||
export const rubricItemIsExtraCredit = (item: RubricItem) => {
|
||||
const extraCredit = "(extra credit)";
|
||||
return item.label.toLowerCase().includes(extraCredit.toLowerCase());
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
verifyDateOrThrow,
|
||||
verifyDateStringOrUndefined,
|
||||
} from "../../../../../models/local/utils/timeUtils";
|
||||
import { AssignmentSubmissionType } from "../assignmentSubmissionType";
|
||||
import { LocalAssignment } from "../localAssignment";
|
||||
import { RubricItem } from "../rubricItem";
|
||||
import { extractLabelValue } from "./markdownUtils";
|
||||
|
||||
const parseFileUploadExtensions = (input: string) => {
|
||||
const allowedFileUploadExtensions: string[] = [];
|
||||
const regex = /- (.+)/;
|
||||
|
||||
const words = input.split("AllowedFileUploadExtensions:");
|
||||
if (words.length < 2) return allowedFileUploadExtensions;
|
||||
|
||||
const inputAfterSubmissionTypes = words[1];
|
||||
const lines = inputAfterSubmissionTypes
|
||||
.split("\n")
|
||||
.map((line) => line.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
const match = regex.exec(line);
|
||||
if (!match) {
|
||||
if (line === "") continue;
|
||||
else break;
|
||||
}
|
||||
|
||||
allowedFileUploadExtensions.push(match[1].trim());
|
||||
}
|
||||
|
||||
return allowedFileUploadExtensions;
|
||||
};
|
||||
|
||||
const parseIndividualRubricItemMarkdown = (rawMarkdown: string) => {
|
||||
const pointsPattern = /\s*-\s*(-?\d+(?:\.\d+)?)\s*pt(s)?:/;
|
||||
const match = pointsPattern.exec(rawMarkdown);
|
||||
if (!match) {
|
||||
throw new Error(`Points not found: ${rawMarkdown}`);
|
||||
}
|
||||
|
||||
const points = parseFloat(match[1]);
|
||||
const label = rawMarkdown.split(": ").slice(1).join(": ");
|
||||
|
||||
const item: RubricItem = { points, label };
|
||||
return item;
|
||||
};
|
||||
|
||||
const parseSettings = (input: string) => {
|
||||
const rawLockAt = extractLabelValue(input, "LockAt");
|
||||
const rawDueAt = extractLabelValue(input, "DueAt");
|
||||
const assignmentGroupName = extractLabelValue(input, "AssignmentGroupName");
|
||||
const submissionTypes = parseSubmissionTypes(input);
|
||||
const fileUploadExtensions = parseFileUploadExtensions(input);
|
||||
const githubClassroomAssignmentShareLink = extractLabelValue(
|
||||
input,
|
||||
"GithubClassroomAssignmentShareLink"
|
||||
);
|
||||
const githubClassroomAssignmentLink = extractLabelValue(
|
||||
input,
|
||||
"GithubClassroomAssignmentLink"
|
||||
);
|
||||
|
||||
const dueAt = verifyDateOrThrow(rawDueAt, "DueAt");
|
||||
const lockAt = verifyDateStringOrUndefined(rawLockAt);
|
||||
|
||||
return {
|
||||
assignmentGroupName,
|
||||
submissionTypes,
|
||||
fileUploadExtensions,
|
||||
dueAt,
|
||||
lockAt,
|
||||
githubClassroomAssignmentShareLink,
|
||||
githubClassroomAssignmentLink,
|
||||
};
|
||||
};
|
||||
|
||||
const parseSubmissionTypes = (input: string): AssignmentSubmissionType[] => {
|
||||
const submissionTypes: AssignmentSubmissionType[] = [];
|
||||
const regex = /- (.+)/;
|
||||
|
||||
const words = input.split("SubmissionTypes:");
|
||||
if (words.length < 2) return submissionTypes;
|
||||
|
||||
const inputAfterSubmissionTypes = words[1]; // doesn't consider other settings that follow...
|
||||
const lines = inputAfterSubmissionTypes
|
||||
.split("\n")
|
||||
.map((line) => line.trim());
|
||||
|
||||
for (const line of lines) {
|
||||
const match = regex.exec(line);
|
||||
if (!match) {
|
||||
if (line === "") continue;
|
||||
else break;
|
||||
}
|
||||
|
||||
const typeString = match[1].trim();
|
||||
const type = Object.values(AssignmentSubmissionType).find(
|
||||
(t) => t === typeString
|
||||
);
|
||||
|
||||
if (type) {
|
||||
submissionTypes.push(type);
|
||||
} else {
|
||||
console.warn(`Unknown submission type: ${typeString}`);
|
||||
}
|
||||
}
|
||||
|
||||
return submissionTypes;
|
||||
};
|
||||
|
||||
const parseRubricMarkdown = (rawMarkdown: string) => {
|
||||
if (!rawMarkdown.trim()) return [];
|
||||
|
||||
const lines = rawMarkdown.trim().split("\n");
|
||||
return lines.map(parseIndividualRubricItemMarkdown);
|
||||
};
|
||||
|
||||
export const assignmentMarkdownParser = {
|
||||
parseRubricMarkdown,
|
||||
parseMarkdown(input: string, name: string): LocalAssignment {
|
||||
const settingsString = input.split("---")[0];
|
||||
const {
|
||||
assignmentGroupName,
|
||||
submissionTypes,
|
||||
fileUploadExtensions,
|
||||
dueAt,
|
||||
lockAt,
|
||||
githubClassroomAssignmentShareLink,
|
||||
githubClassroomAssignmentLink,
|
||||
} = parseSettings(settingsString);
|
||||
|
||||
const description = input
|
||||
.split("---\n")
|
||||
.slice(1)
|
||||
.join("---\n")
|
||||
.split("## Rubric")[0]
|
||||
.trim();
|
||||
|
||||
const rubricString = input.split("## Rubric\n")[1];
|
||||
const rubric = parseRubricMarkdown(rubricString);
|
||||
|
||||
const assignment: LocalAssignment = {
|
||||
name,
|
||||
localAssignmentGroupName: assignmentGroupName.trim(),
|
||||
submissionTypes: submissionTypes,
|
||||
allowedFileUploadExtensions: fileUploadExtensions,
|
||||
dueAt: dueAt,
|
||||
lockAt: lockAt,
|
||||
rubric: rubric,
|
||||
description: description,
|
||||
};
|
||||
if (githubClassroomAssignmentShareLink) {
|
||||
assignment.githubClassroomAssignmentShareLink =
|
||||
githubClassroomAssignmentShareLink;
|
||||
}
|
||||
if (githubClassroomAssignmentLink) {
|
||||
assignment.githubClassroomAssignmentLink = githubClassroomAssignmentLink;
|
||||
}
|
||||
return assignment;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { AssignmentSubmissionType } from "../assignmentSubmissionType";
|
||||
import { LocalAssignment } from "../localAssignment";
|
||||
import { RubricItem } from "../rubricItem";
|
||||
|
||||
const assignmentRubricToMarkdown = (assignment: LocalAssignment) => {
|
||||
return assignment.rubric
|
||||
.map((item: RubricItem) => {
|
||||
const pointLabel = item.points > 1 ? "pts" : "pt";
|
||||
return `- ${item.points}${pointLabel}: ${item.label}`;
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
|
||||
const settingsToMarkdown = (assignment: LocalAssignment) => {
|
||||
const printableDueDate = assignment.dueAt.toString().replace("\u202F", " ");
|
||||
const printableLockAt =
|
||||
assignment.lockAt?.toString().replace("\u202F", " ") || "";
|
||||
|
||||
const submissionTypesMarkdown = assignment.submissionTypes
|
||||
.map((submissionType: AssignmentSubmissionType) => `- ${submissionType}`)
|
||||
.join("\n");
|
||||
|
||||
const allowedFileUploadExtensionsMarkdown =
|
||||
assignment.allowedFileUploadExtensions
|
||||
.map((fileExtension: string) => `- ${fileExtension}`)
|
||||
.join("\n");
|
||||
|
||||
const settingsMarkdownArr = [
|
||||
`LockAt: ${printableLockAt}`,
|
||||
`DueAt: ${printableDueDate}`,
|
||||
`AssignmentGroupName: ${assignment.localAssignmentGroupName}`,
|
||||
`GithubClassroomAssignmentLink: ${assignment.githubClassroomAssignmentLink ?? ""}`,
|
||||
`GithubClassroomAssignmentShareLink: ${assignment.githubClassroomAssignmentShareLink ?? ""}`,
|
||||
`SubmissionTypes:\n${submissionTypesMarkdown}`,
|
||||
`AllowedFileUploadExtensions:\n${allowedFileUploadExtensionsMarkdown}`,
|
||||
];
|
||||
return settingsMarkdownArr.join("\n");
|
||||
};
|
||||
|
||||
export const assignmentMarkdownSerializer = {
|
||||
toMarkdown(assignment: LocalAssignment): string {
|
||||
try {
|
||||
const settingsMarkdown = settingsToMarkdown(assignment);
|
||||
const rubricMarkdown = assignmentRubricToMarkdown(assignment);
|
||||
const assignmentMarkdown = `${settingsMarkdown}\n---\n\n${assignment.description}\n\n## Rubric\n\n${rubricMarkdown}`;
|
||||
|
||||
return assignmentMarkdown;
|
||||
} catch (e) {
|
||||
console.log(assignment);
|
||||
console.log("Error converting assignment to markdown");
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RubricItem } from "../rubricItem";
|
||||
|
||||
export const assignmentPoints = (rubric: RubricItem[]) => {
|
||||
const basePoints = rubric
|
||||
.map((r) => {
|
||||
if (r.label.toLowerCase().includes("(extra credit)")) return 0;
|
||||
if (r.points < 0) return 0; // don't count negative points towards the point totals
|
||||
return r.points;
|
||||
})
|
||||
.reduce((acc, current) => (current > 0 ? acc + current : acc), 0);
|
||||
return basePoints;
|
||||
};
|
||||
10
src/features/local/assignments/models/utils/markdownUtils.ts
Normal file
10
src/features/local/assignments/models/utils/markdownUtils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const extractLabelValue = (input: string, label: string) => {
|
||||
const pattern = new RegExp(`${label}: (.*?)\n`);
|
||||
const match = pattern.exec(input);
|
||||
|
||||
if (match && match.length > 1 && match[1]) {
|
||||
return match[1].trim();
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
Reference in New Issue
Block a user