mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
more layout and cleanup
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
|
||||
import CourseContextProvider from "./context/CourseContextProvider";
|
||||
import { Suspense } from "react";
|
||||
import { getQueryClient } from "@/app/providersQueryClientUtils";
|
||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||
import { hydrateCanvasCourse } from "@/hooks/hookHydration";
|
||||
|
||||
export default async function CourseLayout({
|
||||
children,
|
||||
@@ -13,11 +17,19 @@ export default async function CourseLayout({
|
||||
console.log("cannot load course that is .js.map " + decodedCourseName);
|
||||
return <div></div>;
|
||||
}
|
||||
const settings = await fileStorageService.settings.getCourseSettings(
|
||||
decodedCourseName
|
||||
);
|
||||
const queryClient = getQueryClient();
|
||||
await hydrateCanvasCourse(settings.canvasId, queryClient);
|
||||
const dehydratedState = dehydrate(queryClient);
|
||||
return (
|
||||
<Suspense>
|
||||
<CourseContextProvider localCourseName={decodedCourseName}>
|
||||
{children}
|
||||
</CourseContextProvider>
|
||||
<HydrationBoundary state={dehydratedState}>
|
||||
<CourseContextProvider localCourseName={decodedCourseName}>
|
||||
{children}
|
||||
</CourseContextProvider>
|
||||
</HydrationBoundary>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function AssignmentPreview({
|
||||
assignment: LocalAssignment;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full overflow-y-auto">
|
||||
<section>
|
||||
<div className="flex">
|
||||
<div className="flex-1 text-end pe-3">Due Date</div>
|
||||
|
||||
@@ -21,6 +21,8 @@ import { Spinner } from "@/components/Spinner";
|
||||
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
|
||||
import ClientOnly from "@/components/ClientOnly";
|
||||
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
||||
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
|
||||
import { LocalCourseSettings } from "@/models/local/localCourse";
|
||||
|
||||
export default function EditAssignment({
|
||||
moduleName,
|
||||
@@ -29,6 +31,7 @@ export default function EditAssignment({
|
||||
assignmentName: string;
|
||||
moduleName: string;
|
||||
}) {
|
||||
const { data: settings } = useLocalCourseSettingsQuery();
|
||||
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
|
||||
const updateAssignment = useUpdateAssignmentMutation();
|
||||
|
||||
@@ -36,6 +39,7 @@ export default function EditAssignment({
|
||||
localAssignmentMarkdown.toMarkdown(assignment)
|
||||
);
|
||||
const [error, setError] = useState("");
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 500;
|
||||
@@ -72,14 +76,23 @@ export default function EditAssignment({
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="columns-2 min-h-0 flex-1">
|
||||
<div className="h-full flex flex-col align-middle px-1">
|
||||
<div className={"min-h-96 flex flex-row w-full"}>
|
||||
{showHelp && (
|
||||
<pre className=" max-w-96">
|
||||
<code>{getHelpString(settings)}</code>
|
||||
</pre>
|
||||
)}
|
||||
<div className="flex-1 h-full">
|
||||
<MonacoEditor value={assignmentText} onChange={setAssignmentText} />
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<div className="flex-1 h-full">
|
||||
<div className="text-red-300">{error && error}</div>
|
||||
|
||||
<div className="px-3 h-full">
|
||||
|
||||
<AssignmentPreview assignment={assignment} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
@@ -87,6 +100,7 @@ export default function EditAssignment({
|
||||
<AssignmentButtons
|
||||
moduleName={moduleName}
|
||||
assignmentName={assignmentName}
|
||||
toggleHelp={() => setShowHelp((h) => !h)}
|
||||
/>
|
||||
</SuspenseAndErrorHandling>
|
||||
</ClientOnly>
|
||||
@@ -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 (
|
||||
<div className="p-5 flex flex-row justify-end gap-3">
|
||||
{(addToCanvas.isPending ||
|
||||
deleteFromCanvas.isPending ||
|
||||
updateAssignment.isPending) && <Spinner />}
|
||||
{assignmentInCanvas && !assignmentInCanvas.published && (
|
||||
<div className="text-rose-300 my-auto">Not Published</div>
|
||||
)}
|
||||
{!assignmentInCanvas && (
|
||||
<button
|
||||
disabled={addToCanvas.isPending}
|
||||
onClick={() => addToCanvas.mutate(assignment)}
|
||||
>
|
||||
Add to canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<a
|
||||
className="btn"
|
||||
target="_blank"
|
||||
href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`}
|
||||
>
|
||||
View in Canvas
|
||||
</a>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className=""
|
||||
disabled={deleteFromCanvas.isPending}
|
||||
onClick={() =>
|
||||
updateAssignment.mutate({
|
||||
canvasAssignmentId: assignmentInCanvas.id,
|
||||
assignment,
|
||||
})
|
||||
}
|
||||
>
|
||||
Update in Canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className="btn-danger"
|
||||
disabled={deleteFromCanvas.isPending}
|
||||
onClick={() =>
|
||||
deleteFromCanvas.mutate({
|
||||
canvasAssignmentId: assignmentInCanvas.id,
|
||||
assignmentName: assignment.name,
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete from Canvas
|
||||
</button>
|
||||
)}
|
||||
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
|
||||
Go Back
|
||||
</Link>
|
||||
<div className="p-5 flex flex-row justify-between gap-3">
|
||||
<div>
|
||||
<button onClick={toggleHelp}>Toggle Help</button>
|
||||
</div>
|
||||
<div className="flex flex-row gap-3 justify-end">
|
||||
{(addToCanvas.isPending ||
|
||||
deleteFromCanvas.isPending ||
|
||||
updateAssignment.isPending) && <Spinner />}
|
||||
{assignmentInCanvas && !assignmentInCanvas.published && (
|
||||
<div className="text-rose-300 my-auto">Not Published</div>
|
||||
)}
|
||||
{!assignmentInCanvas && (
|
||||
<button
|
||||
disabled={addToCanvas.isPending}
|
||||
onClick={() => addToCanvas.mutate(assignment)}
|
||||
>
|
||||
Add to canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<a
|
||||
className="btn"
|
||||
target="_blank"
|
||||
href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`}
|
||||
>
|
||||
View in Canvas
|
||||
</a>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className=""
|
||||
disabled={deleteFromCanvas.isPending}
|
||||
onClick={() =>
|
||||
updateAssignment.mutate({
|
||||
canvasAssignmentId: assignmentInCanvas.id,
|
||||
assignment,
|
||||
})
|
||||
}
|
||||
>
|
||||
Update in Canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className="btn-danger"
|
||||
disabled={deleteFromCanvas.isPending}
|
||||
onClick={() =>
|
||||
deleteFromCanvas.mutate({
|
||||
canvasAssignmentId: assignmentInCanvas.id,
|
||||
assignmentName: assignment.name,
|
||||
})
|
||||
}
|
||||
>
|
||||
Delete from Canvas
|
||||
</button>
|
||||
)}
|
||||
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
|
||||
Go Back
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
}
|
||||
.monaco-editor { position: absolute !important; }
|
||||
|
||||
.monaco-editor .mtk1 {
|
||||
@apply text-slate-300;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-4xl font-bold my-1;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
return dateToMarkdownString(date).split(" ")[0];
|
||||
};
|
||||
|
||||
@@ -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<CanvasAssignmentGroup[]> {
|
||||
console.log("Requesting assignment groups");
|
||||
const url = `${canvasApi}/courses/${courseId}/assignment_groups`;
|
||||
const assignmentGroups = await canvasServiceUtils.paginatedRequest<
|
||||
const assignmentGroups = await paginatedRequest<
|
||||
CanvasAssignmentGroup[]
|
||||
>({
|
||||
url,
|
||||
|
||||
@@ -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<CanvasRubricCreationResponse>(
|
||||
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<CanvasAssignment[]> {
|
||||
@@ -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<CanvasRubricCreationResponse>(
|
||||
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
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<T>(request: {
|
||||
export async function paginatedRequest<T extends any[]>(request: {
|
||||
url: string;
|
||||
}): Promise<T> {
|
||||
var requestCount = 1;
|
||||
@@ -36,20 +44,19 @@ export async function paginatedRequest<T>(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<T>(nextUrl);
|
||||
if (data) {
|
||||
returnData = [...returnData, data];
|
||||
returnData = returnData.concat(data);
|
||||
}
|
||||
nextUrl = getNextUrl(headers);
|
||||
}
|
||||
@@ -60,5 +67,5 @@ export async function paginatedRequest<T>(request: {
|
||||
);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
return returnData as T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user