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 CourseContextProvider from "./context/CourseContextProvider";
|
||||||
import { Suspense } from "react";
|
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({
|
export default async function CourseLayout({
|
||||||
children,
|
children,
|
||||||
@@ -13,11 +17,19 @@ export default async function CourseLayout({
|
|||||||
console.log("cannot load course that is .js.map " + decodedCourseName);
|
console.log("cannot load course that is .js.map " + decodedCourseName);
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
const settings = await fileStorageService.settings.getCourseSettings(
|
||||||
|
decodedCourseName
|
||||||
|
);
|
||||||
|
const queryClient = getQueryClient();
|
||||||
|
await hydrateCanvasCourse(settings.canvasId, queryClient);
|
||||||
|
const dehydratedState = dehydrate(queryClient);
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<CourseContextProvider localCourseName={decodedCourseName}>
|
<HydrationBoundary state={dehydratedState}>
|
||||||
{children}
|
<CourseContextProvider localCourseName={decodedCourseName}>
|
||||||
</CourseContextProvider>
|
{children}
|
||||||
|
</CourseContextProvider>
|
||||||
|
</HydrationBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function AssignmentPreview({
|
|||||||
assignment: LocalAssignment;
|
assignment: LocalAssignment;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="h-full overflow-y-auto">
|
||||||
<section>
|
<section>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-1 text-end pe-3">Due Date</div>
|
<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 { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
|
||||||
import ClientOnly from "@/components/ClientOnly";
|
import ClientOnly from "@/components/ClientOnly";
|
||||||
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
import { SuspenseAndErrorHandling } from "@/components/SuspenseAndErrorHandling";
|
||||||
|
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
|
||||||
|
import { LocalCourseSettings } from "@/models/local/localCourse";
|
||||||
|
|
||||||
export default function EditAssignment({
|
export default function EditAssignment({
|
||||||
moduleName,
|
moduleName,
|
||||||
@@ -29,6 +31,7 @@ export default function EditAssignment({
|
|||||||
assignmentName: string;
|
assignmentName: string;
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { data: settings } = useLocalCourseSettingsQuery();
|
||||||
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
|
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
|
||||||
const updateAssignment = useUpdateAssignmentMutation();
|
const updateAssignment = useUpdateAssignmentMutation();
|
||||||
|
|
||||||
@@ -36,6 +39,7 @@ export default function EditAssignment({
|
|||||||
localAssignmentMarkdown.toMarkdown(assignment)
|
localAssignmentMarkdown.toMarkdown(assignment)
|
||||||
);
|
);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [showHelp, setShowHelp] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delay = 500;
|
const delay = 500;
|
||||||
@@ -72,14 +76,23 @@ export default function EditAssignment({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col align-middle px-1">
|
||||||
<div className="columns-2 min-h-0 flex-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">
|
<div className="flex-1 h-full">
|
||||||
<MonacoEditor value={assignmentText} onChange={setAssignmentText} />
|
<MonacoEditor value={assignmentText} onChange={setAssignmentText} />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full">
|
<div className="flex-1 h-full">
|
||||||
<div className="text-red-300">{error && error}</div>
|
<div className="text-red-300">{error && error}</div>
|
||||||
|
|
||||||
|
<div className="px-3 h-full">
|
||||||
|
|
||||||
<AssignmentPreview assignment={assignment} />
|
<AssignmentPreview assignment={assignment} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
@@ -87,6 +100,7 @@ export default function EditAssignment({
|
|||||||
<AssignmentButtons
|
<AssignmentButtons
|
||||||
moduleName={moduleName}
|
moduleName={moduleName}
|
||||||
assignmentName={assignmentName}
|
assignmentName={assignmentName}
|
||||||
|
toggleHelp={() => setShowHelp((h) => !h)}
|
||||||
/>
|
/>
|
||||||
</SuspenseAndErrorHandling>
|
</SuspenseAndErrorHandling>
|
||||||
</ClientOnly>
|
</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({
|
function AssignmentButtons({
|
||||||
moduleName,
|
moduleName,
|
||||||
assignmentName,
|
assignmentName,
|
||||||
|
toggleHelp,
|
||||||
}: {
|
}: {
|
||||||
assignmentName: string;
|
assignmentName: string;
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
|
toggleHelp: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { courseName } = useCourseContext();
|
const { courseName } = useCourseContext();
|
||||||
const { data: settings } = useLocalCourseSettingsQuery();
|
const { data: settings } = useLocalCourseSettingsQuery();
|
||||||
@@ -113,61 +145,66 @@ function AssignmentButtons({
|
|||||||
(a) => a.name === assignmentName
|
(a) => a.name === assignmentName
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="p-5 flex flex-row justify-end gap-3">
|
<div className="p-5 flex flex-row justify-between gap-3">
|
||||||
{(addToCanvas.isPending ||
|
<div>
|
||||||
deleteFromCanvas.isPending ||
|
<button onClick={toggleHelp}>Toggle Help</button>
|
||||||
updateAssignment.isPending) && <Spinner />}
|
</div>
|
||||||
{assignmentInCanvas && !assignmentInCanvas.published && (
|
<div className="flex flex-row gap-3 justify-end">
|
||||||
<div className="text-rose-300 my-auto">Not Published</div>
|
{(addToCanvas.isPending ||
|
||||||
)}
|
deleteFromCanvas.isPending ||
|
||||||
{!assignmentInCanvas && (
|
updateAssignment.isPending) && <Spinner />}
|
||||||
<button
|
{assignmentInCanvas && !assignmentInCanvas.published && (
|
||||||
disabled={addToCanvas.isPending}
|
<div className="text-rose-300 my-auto">Not Published</div>
|
||||||
onClick={() => addToCanvas.mutate(assignment)}
|
)}
|
||||||
>
|
{!assignmentInCanvas && (
|
||||||
Add to canvas
|
<button
|
||||||
</button>
|
disabled={addToCanvas.isPending}
|
||||||
)}
|
onClick={() => addToCanvas.mutate(assignment)}
|
||||||
{assignmentInCanvas && (
|
>
|
||||||
<a
|
Add to canvas
|
||||||
className="btn"
|
</button>
|
||||||
target="_blank"
|
)}
|
||||||
href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`}
|
{assignmentInCanvas && (
|
||||||
>
|
<a
|
||||||
View in Canvas
|
className="btn"
|
||||||
</a>
|
target="_blank"
|
||||||
)}
|
href={`${baseCanvasUrl}/courses/${settings.canvasId}/assignments/${assignmentInCanvas.id}`}
|
||||||
{assignmentInCanvas && (
|
>
|
||||||
<button
|
View in Canvas
|
||||||
className=""
|
</a>
|
||||||
disabled={deleteFromCanvas.isPending}
|
)}
|
||||||
onClick={() =>
|
{assignmentInCanvas && (
|
||||||
updateAssignment.mutate({
|
<button
|
||||||
canvasAssignmentId: assignmentInCanvas.id,
|
className=""
|
||||||
assignment,
|
disabled={deleteFromCanvas.isPending}
|
||||||
})
|
onClick={() =>
|
||||||
}
|
updateAssignment.mutate({
|
||||||
>
|
canvasAssignmentId: assignmentInCanvas.id,
|
||||||
Update in Canvas
|
assignment,
|
||||||
</button>
|
})
|
||||||
)}
|
}
|
||||||
{assignmentInCanvas && (
|
>
|
||||||
<button
|
Update in Canvas
|
||||||
className="btn-danger"
|
</button>
|
||||||
disabled={deleteFromCanvas.isPending}
|
)}
|
||||||
onClick={() =>
|
{assignmentInCanvas && (
|
||||||
deleteFromCanvas.mutate({
|
<button
|
||||||
canvasAssignmentId: assignmentInCanvas.id,
|
className="btn-danger"
|
||||||
assignmentName: assignment.name,
|
disabled={deleteFromCanvas.isPending}
|
||||||
})
|
onClick={() =>
|
||||||
}
|
deleteFromCanvas.mutate({
|
||||||
>
|
canvasAssignmentId: assignmentInCanvas.id,
|
||||||
Delete from Canvas
|
assignmentName: assignment.name,
|
||||||
</button>
|
})
|
||||||
)}
|
}
|
||||||
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
|
>
|
||||||
Go Back
|
Delete from Canvas
|
||||||
</Link>
|
</button>
|
||||||
|
)}
|
||||||
|
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
|
||||||
|
Go Back
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
}
|
}
|
||||||
.monaco-editor { position: absolute !important; }
|
.monaco-editor { position: absolute !important; }
|
||||||
|
|
||||||
|
.monaco-editor .mtk1 {
|
||||||
|
@apply text-slate-300;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-4xl font-bold my-1;
|
@apply text-4xl font-bold my-1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { getQueryClient } from "./providersQueryClientUtils";
|
|||||||
import { hydrateCourses } from "@/hooks/hookHydration";
|
import { hydrateCourses } from "@/hooks/hookHydration";
|
||||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||||
import { MyToaster } from "./MyToaster";
|
import { MyToaster } from "./MyToaster";
|
||||||
import "katex/dist/katex.min.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Canvas Manager 2.0",
|
title: "Canvas Manager 2.0",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { LocalAssignment } from "@/models/local/assignment/localAssignment";
|
|||||||
export const canvasAssignmentKeys = {
|
export const canvasAssignmentKeys = {
|
||||||
assignments: (canvasCourseId: number) =>
|
assignments: (canvasCourseId: number) =>
|
||||||
["canvas", canvasCourseId, "assignments"] as const,
|
["canvas", canvasCourseId, "assignments"] as const,
|
||||||
assignment: (canvasCourseId: number, assignmentName: string) =>
|
// assignment: (canvasCourseId: number, assignmentName: string) =>
|
||||||
["canvas", canvasCourseId, "assignment", assignmentName] as const,
|
// ["canvas", canvasCourseId, "assignment", assignmentName] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCanvasAssignmentsQuery = () => {
|
export const useCanvasAssignmentsQuery = () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { canvasService } from "@/services/canvas/canvasService";
|
import { canvasService } from "@/services/canvas/canvasService";
|
||||||
import { QueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { canvasCourseModuleKeys } from "./canvasModuleHooks";
|
|
||||||
|
|
||||||
export const canvasKeys = {
|
export const canvasKeys = {
|
||||||
allTerms: ["all canvas terms"] as const,
|
allTerms: ["all canvas terms"] as const,
|
||||||
|
|||||||
@@ -2,10 +2,18 @@ import { QueryClient } from "@tanstack/react-query";
|
|||||||
import { localCourseKeys } from "./localCourse/localCourseKeys";
|
import { localCourseKeys } from "./localCourse/localCourseKeys";
|
||||||
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
|
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
|
||||||
import { LocalCourseSettings } from "@/models/local/localCourse";
|
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
|
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr
|
||||||
export const hydrateCourses = async (queryClient: QueryClient) => {
|
export const hydrateCourses = async (queryClient: QueryClient) => {
|
||||||
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
|
const allSettings = await fileStorageService.settings.getAllCoursesSettings();
|
||||||
const courseNames = allSettings.map((s) => s.name);
|
|
||||||
await queryClient.prefetchQuery({
|
await queryClient.prefetchQuery({
|
||||||
queryKey: localCourseKeys.allCoursesSettings,
|
queryKey: localCourseKeys.allCoursesSettings,
|
||||||
queryFn: () => allSettings,
|
queryFn: () => allSettings,
|
||||||
@@ -27,51 +35,7 @@ export const hydrateCourse = async (
|
|||||||
courseName
|
courseName
|
||||||
);
|
);
|
||||||
const modulesData = await Promise.all(
|
const modulesData = await Promise.all(
|
||||||
moduleNames.map(async (moduleName) => {
|
moduleNames.map((moduleName) => loadAllModuleData(courseName, 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,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await queryClient.prefetchQuery({
|
await queryClient.prefetchQuery({
|
||||||
@@ -84,68 +48,146 @@ export const hydrateCourse = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
modulesData.map(
|
modulesData.map((d) => hydrateModuleData(d, courseName, queryClient))
|
||||||
async ({
|
);
|
||||||
moduleName,
|
};
|
||||||
assignmentNames,
|
|
||||||
pageNames,
|
export const hydrateCanvasCourse = async (
|
||||||
quizNames,
|
canvasCourseId: number,
|
||||||
assignments,
|
queryClient: QueryClient
|
||||||
quizzes,
|
) => {
|
||||||
pages,
|
await Promise.all([
|
||||||
}) => {
|
queryClient.prefetchQuery({
|
||||||
await queryClient.prefetchQuery({
|
queryKey: canvasAssignmentKeys.assignments(canvasCourseId),
|
||||||
queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
|
queryFn: async () => await canvasAssignmentService.getAll(canvasCourseId),
|
||||||
queryFn: () => assignmentNames,
|
}),
|
||||||
});
|
queryClient.prefetchQuery({
|
||||||
await Promise.all(
|
queryKey: canvasQuizKeys.quizzes(canvasCourseId),
|
||||||
assignments.map(
|
queryFn: async () => await canvasQuizService.getAll(canvasCourseId),
|
||||||
async (assignment) =>
|
}),
|
||||||
await queryClient.prefetchQuery({
|
queryClient.prefetchQuery({
|
||||||
queryKey: localCourseKeys.assignment(
|
queryKey: canvasPageKeys.pagesInCourse(canvasCourseId),
|
||||||
courseName,
|
queryFn: async () => await canvasPageService.getAll(canvasCourseId),
|
||||||
moduleName,
|
}),
|
||||||
assignment.name
|
]);
|
||||||
),
|
};
|
||||||
queryFn: () => assignment,
|
|
||||||
})
|
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),
|
await Promise.all(
|
||||||
queryFn: () => quizNames,
|
quizNames.map(
|
||||||
});
|
async (quizName) =>
|
||||||
await Promise.all(
|
await fileStorageService.quizzes.getQuiz(
|
||||||
quizzes.map(
|
courseName,
|
||||||
async (quiz) =>
|
moduleName,
|
||||||
await queryClient.prefetchQuery({
|
quizName
|
||||||
queryKey: localCourseKeys.quiz(
|
|
||||||
courseName,
|
|
||||||
moduleName,
|
|
||||||
quiz.name
|
|
||||||
),
|
|
||||||
queryFn: () => quiz,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
await queryClient.prefetchQuery({
|
),
|
||||||
queryKey: localCourseKeys.pageNames(courseName, moduleName),
|
await Promise.all(
|
||||||
queryFn: () => pageNames,
|
pageNames.map(
|
||||||
});
|
async (pageName) =>
|
||||||
await Promise.all(
|
await fileStorageService.pages.getPage(
|
||||||
pages.map(
|
courseName,
|
||||||
async (page) =>
|
moduleName,
|
||||||
await queryClient.prefetchQuery({
|
pageName
|
||||||
queryKey: localCourseKeys.page(
|
|
||||||
courseName,
|
|
||||||
moduleName,
|
|
||||||
page.name
|
|
||||||
),
|
|
||||||
queryFn: () => page,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
}
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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 => {
|
export const getDateFromString = (value: string): Date | undefined => {
|
||||||
|
|
||||||
const ampmDateRegex =
|
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"
|
/^\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"
|
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(" ");
|
const [datePart, timePart] = value.split(" ");
|
||||||
return _getDateFromMilitary(datePart, timePart);
|
return _getDateFromMilitary(datePart, timePart);
|
||||||
} else {
|
} else {
|
||||||
console.log("invalid date format", value);
|
if (value) console.log("invalid date format", value);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,7 +92,6 @@ export const dateToMarkdownString = (date: Date) => {
|
|||||||
return `${stringMonth}/${stringDay}/${stringYear} ${stringHours}:${stringMinutes}:${stringSeconds}`;
|
return `${stringMonth}/${stringDay}/${stringYear} ${stringHours}:${stringMinutes}:${stringSeconds}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const getDateOnlyMarkdownString = (date: Date) => {
|
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 { axiosClient } from "../axiosUtils";
|
||||||
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
|
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
|
||||||
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
|
||||||
@@ -8,7 +8,7 @@ export const canvasAssignmentGroupService = {
|
|||||||
async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> {
|
async getAll(courseId: number): Promise<CanvasAssignmentGroup[]> {
|
||||||
console.log("Requesting assignment groups");
|
console.log("Requesting assignment groups");
|
||||||
const url = `${canvasApi}/courses/${courseId}/assignment_groups`;
|
const url = `${canvasApi}/courses/${courseId}/assignment_groups`;
|
||||||
const assignmentGroups = await canvasServiceUtils.paginatedRequest<
|
const assignmentGroups = await paginatedRequest<
|
||||||
CanvasAssignmentGroup[]
|
CanvasAssignmentGroup[]
|
||||||
>({
|
>({
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -1,75 +1,12 @@
|
|||||||
import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment";
|
import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment";
|
||||||
import {
|
import { canvasApi, paginatedRequest } from "./canvasServiceUtils";
|
||||||
canvasApi,
|
|
||||||
canvasServiceUtils,
|
|
||||||
paginatedRequest,
|
|
||||||
} from "./canvasServiceUtils";
|
|
||||||
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
|
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
|
||||||
import { axiosClient } from "../axiosUtils";
|
import { axiosClient } from "../axiosUtils";
|
||||||
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
|
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
|
||||||
import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse";
|
import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse";
|
||||||
import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils";
|
import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils";
|
||||||
import {
|
import { getDateFromString } from "@/models/local/timeUtils";
|
||||||
getDateFromString,
|
|
||||||
getDateFromStringOrThrow,
|
|
||||||
} 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 = {
|
export const canvasAssignmentService = {
|
||||||
async getAll(courseId: number): Promise<CanvasAssignment[]> {
|
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.get("link") as string)
|
||||||
: ((headers as RawAxiosResponseHeaders)["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 links = linkHeader.split(",").map((link) => link.trim());
|
||||||
const nextLink = links.find((link) => link.includes('rel="next"'));
|
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);
|
const nextUrl = nextLink.split(";")[0].trim().slice(1, -1);
|
||||||
return nextUrl;
|
return nextUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function paginatedRequest<T>(request: {
|
export async function paginatedRequest<T extends any[]>(request: {
|
||||||
url: string;
|
url: string;
|
||||||
}): Promise<T> {
|
}): Promise<T> {
|
||||||
var requestCount = 1;
|
var requestCount = 1;
|
||||||
@@ -36,20 +44,19 @@ export async function paginatedRequest<T>(request: {
|
|||||||
url.toString()
|
url.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!Array.isArray(firstData)) {
|
// if (!Array.isArray(firstData)) {
|
||||||
return firstData;
|
// return firstData;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
var returnData = [...firstData];
|
||||||
var returnData = firstData ? [firstData] : [];
|
|
||||||
var nextUrl = getNextUrl(firstHeaders);
|
var nextUrl = getNextUrl(firstHeaders);
|
||||||
console.log("got first request", nextUrl, firstHeaders);
|
// console.log("got first request", nextUrl, firstHeaders);
|
||||||
|
|
||||||
while (nextUrl) {
|
while (nextUrl) {
|
||||||
requestCount += 1;
|
requestCount += 1;
|
||||||
const { data, headers } = await axiosClient.get<T>(nextUrl);
|
const { data, headers } = await axiosClient.get<T>(nextUrl);
|
||||||
if (data) {
|
if (data) {
|
||||||
returnData = [...returnData, data];
|
returnData = returnData.concat(data);
|
||||||
}
|
}
|
||||||
nextUrl = getNextUrl(headers);
|
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