can drag and drop quizzes and assignments

This commit is contained in:
2024-09-02 14:26:48 -06:00
parent 4f44349db3
commit 13f20b5970
7 changed files with 204 additions and 20 deletions

View File

@@ -15,3 +15,19 @@ export async function GET(
); );
return Response.json(settings); return Response.json(settings);
} }
export async function PUT(
request: Request,
{
params: { courseName, moduleName, assignmentName },
}: { params: { courseName: string; moduleName: string; assignmentName: string } }
) {
const assignment = await request.json()
await fileStorageService.updateAssignment(
courseName,
moduleName,
assignmentName,
assignment
);
return Response.json({});
}

View File

@@ -13,3 +13,19 @@ export async function GET(
); );
return Response.json(settings); return Response.json(settings);
} }
export async function PUT(
request: Request,
{
params: { courseName, moduleName, pageName },
}: { params: { courseName: string; moduleName: string; pageName: string } }
) {
const page = await request.json()
await fileStorageService.updatePage(
courseName,
moduleName,
pageName,
page
);
return Response.json({});
}

View File

@@ -4,7 +4,14 @@ import { DraggingContext } from "./draggingContext";
import { useUpdateQuizMutation } from "@/hooks/localCourse/quizHooks"; import { useUpdateQuizMutation } from "@/hooks/localCourse/quizHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { LocalQuiz } from "@/models/local/quiz/localQuiz"; import { LocalQuiz } from "@/models/local/quiz/localQuiz";
import { getDateFromStringOrThrow, dateToMarkdownString } from "@/models/local/timeUtils"; import {
getDateFromStringOrThrow,
dateToMarkdownString,
} from "@/models/local/timeUtils";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks";
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
export default function DraggingContextProvider({ export default function DraggingContextProvider({
children, children,
@@ -13,6 +20,8 @@ export default function DraggingContextProvider({
localCourseName: string; localCourseName: string;
}) { }) {
const updateQuizMutation = useUpdateQuizMutation(); const updateQuizMutation = useUpdateQuizMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();
const updatePageMutation = useUpdatePageMutation();
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
const itemDrop = useCallback( const itemDrop = useCallback(
@@ -35,12 +44,7 @@ export default function DraggingContextProvider({
const quiz: LocalQuiz = { const quiz: LocalQuiz = {
...previousQuiz, ...previousQuiz,
dueAt: dateToMarkdownString(dayAsDate), dueAt: dateToMarkdownString(dayAsDate),
lockAt: lockAt: getLaterDate(previousQuiz.lockAt, dayAsDate),
previousQuiz.lockAt &&
(getDateFromStringOrThrow(previousQuiz.lockAt, "lockAt date") >
dayAsDate
? previousQuiz.lockAt
: dateToMarkdownString(dayAsDate)),
}; };
updateQuizMutation.mutate({ updateQuizMutation.mutate({
quiz: quiz, quiz: quiz,
@@ -48,15 +52,46 @@ export default function DraggingContextProvider({
moduleName: itemBeingDragged.sourceModuleName, moduleName: itemBeingDragged.sourceModuleName,
}); });
} else if (itemBeingDragged.type === "assignment") { } else if (itemBeingDragged.type === "assignment") {
console.log("dropped assignment"); updateAssignment(dayAsDate);
} else if (itemBeingDragged.type === "page") { } else if (itemBeingDragged.type === "page") {
console.log("dropped page"); console.log("dropped page");
const previousPage = itemBeingDragged.item as LocalCoursePage;
const page: LocalCoursePage = {
...previousPage,
dueAt: dateToMarkdownString(dayAsDate),
};
updatePageMutation.mutate({
page,
moduleName: itemBeingDragged.sourceModuleName,
pageName: page.name,
});
} }
} }
function updateAssignment(dayAsDate: Date) {
const previousAssignment = itemBeingDragged.item as LocalAssignment;
const assignment: LocalAssignment = {
...previousAssignment,
dueAt: dateToMarkdownString(dayAsDate),
lockAt: previousAssignment.lockAt &&
(getDateFromStringOrThrow(
previousAssignment.lockAt,
"lockAt date"
) > dayAsDate
? previousAssignment.lockAt
: dateToMarkdownString(dayAsDate)),
};
updateAssignmentMutation.mutate({
assignment,
moduleName: itemBeingDragged.sourceModuleName,
assignmentName: assignment.name,
});
}
}, },
[ [
settings.defaultDueTime.hour, settings.defaultDueTime.hour,
settings.defaultDueTime.minute, settings.defaultDueTime.minute,
updateAssignmentMutation,
updateQuizMutation, updateQuizMutation,
] ]
); );
@@ -71,3 +106,14 @@ export default function DraggingContextProvider({
</DraggingContext.Provider> </DraggingContext.Provider>
); );
} }
function getLaterDate(
firstDate: string | undefined,
dayAsDate: Date
): string | undefined {
return (
firstDate &&
(getDateFromStringOrThrow(firstDate, "lockAt date") > dayAsDate
? firstDate
: dateToMarkdownString(dayAsDate))
);
}

View File

@@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { localCourseKeys } from "./localCourseKeys"; import { localCourseKeys } from "./localCourseKeys";
import { LocalAssignment } from "@/models/local/assignment/localAssignment"; import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { useSuspenseQuery, useSuspenseQueries } from "@tanstack/react-query"; import { useSuspenseQuery, useSuspenseQueries, useQueryClient, useMutation } from "@tanstack/react-query";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
export const useAssignmentNamesQuery = (moduleName: string) => { export const useAssignmentNamesQuery = (moduleName: string) => {
@@ -72,3 +72,40 @@ export const useAssignmentsQueries = (
}), }),
}); });
}; };
export const useUpdateAssignmentMutation = () => {
const { courseName } = useCourseContext();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
assignment,
moduleName,
assignmentName,
}: {
assignment: LocalAssignment;
moduleName: string;
assignmentName: string;
}) => {
queryClient.setQueryData(
localCourseKeys.assignment(courseName, moduleName, assignmentName),
assignment
);
const url =
"/api/courses/" +
encodeURIComponent(courseName) +
"/modules/" +
encodeURIComponent(moduleName) +
"/assignments/" +
encodeURIComponent(assignmentName);
await axios.put(url, assignment);
},
onSuccess: (_, { moduleName, assignmentName }) => {
queryClient.invalidateQueries({
queryKey: localCourseKeys.assignment(courseName, moduleName, assignmentName),
});
queryClient.invalidateQueries({
queryKey: localCourseKeys.assignmentNames(courseName, moduleName),
});
},
});
};

View File

@@ -1,6 +1,11 @@
"use client" "use client";
import { LocalCoursePage } from "@/models/local/page/localCoursePage"; import { LocalCoursePage } from "@/models/local/page/localCoursePage";
import { useSuspenseQueries, useSuspenseQuery } from "@tanstack/react-query"; import {
useMutation,
useQueryClient,
useSuspenseQueries,
useSuspenseQuery,
} from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import { localCourseKeys } from "./localCourseKeys"; import { localCourseKeys } from "./localCourseKeys";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
@@ -65,3 +70,40 @@ function getPageQueryConfig(
}, },
}; };
} }
export const useUpdatePageMutation = () => {
const { courseName } = useCourseContext();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
page,
moduleName,
pageName,
}: {
page: LocalCoursePage;
moduleName: string;
pageName: string;
}) => {
queryClient.setQueryData(
localCourseKeys.page(courseName, moduleName, pageName),
page
);
const url =
"/api/courses/" +
encodeURIComponent(courseName) +
"/modules/" +
encodeURIComponent(moduleName) +
"/pages/" +
encodeURIComponent(pageName);
await axios.put(url, page);
},
onSuccess: (_, { moduleName, pageName }) => {
queryClient.invalidateQueries({
queryKey: localCourseKeys.page(courseName, moduleName, pageName),
});
queryClient.invalidateQueries({
queryKey: localCourseKeys.pageNames(courseName, moduleName),
});
},
});
};

View File

@@ -79,6 +79,10 @@ export const useUpdateQuizMutation = () => {
moduleName: string; moduleName: string;
quizName: string; quizName: string;
}) => { }) => {
queryClient.setQueryData(
localCourseKeys.quiz(courseName, moduleName, quizName),
quiz
);
const url = const url =
"/api/courses/" + "/api/courses/" +
encodeURIComponent(courseName) + encodeURIComponent(courseName) +
@@ -86,19 +90,15 @@ export const useUpdateQuizMutation = () => {
encodeURIComponent(moduleName) + encodeURIComponent(moduleName) +
"/quizzes/" + "/quizzes/" +
encodeURIComponent(quizName); encodeURIComponent(quizName);
queryClient.setQueryData(
localCourseKeys.quiz(courseName, moduleName, quizName),
quiz
);
await axios.put(url, quiz); await axios.put(url, quiz);
}, },
onSuccess: (_, { moduleName, quizName }) => { onSuccess: (_, { moduleName, quizName }) => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: localCourseKeys.quiz(courseName, moduleName, quizName), queryKey: localCourseKeys.quiz(courseName, moduleName, quizName),
}); });
// queryClient.invalidateQueries({ queryClient.invalidateQueries({
// queryKey: localCourseKeys.quizNames(courseName, moduleName), queryKey: localCourseKeys.quizNames(courseName, moduleName),
// }); });
}, },
}); });
}; };

View File

@@ -9,10 +9,11 @@ import {
directoryOrFileExists, directoryOrFileExists,
hasFileSystemEntries, hasFileSystemEntries,
} from "./utils/fileSystemUtils"; } from "./utils/fileSystemUtils";
import { localAssignmentMarkdown } from "@/models/local/assignment/localAssignment"; import { LocalAssignment, localAssignmentMarkdown } from "@/models/local/assignment/localAssignment";
import { LocalQuiz, localQuizMarkdownUtils } from "@/models/local/quiz/localQuiz"; import { LocalQuiz, localQuizMarkdownUtils } from "@/models/local/quiz/localQuiz";
import { localPageMarkdownUtils } from "@/models/local/page/localCoursePage"; import { LocalCoursePage, localPageMarkdownUtils } from "@/models/local/page/localCoursePage";
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils"; import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
import { assignmentMarkdownSerializer } from "@/models/local/assignment/utils/assignmentMarkdownSerializer";
const basePath = process.env.STORAGE_DIRECTORY ?? "./storage"; const basePath = process.env.STORAGE_DIRECTORY ?? "./storage";
console.log("base path", basePath); console.log("base path", basePath);
@@ -136,6 +137,19 @@ export const fileStorageService = {
); );
return localAssignmentMarkdown.parseMarkdown(rawFile); return localAssignmentMarkdown.parseMarkdown(rawFile);
}, },
async updateAssignment(courseName: string, moduleName: string, assignmentName: string, assignment: LocalAssignment) {
const filePath = path.join(
basePath,
courseName,
moduleName,
"assignments",
assignmentName+".md"
);
const assignmentMarkdown = assignmentMarkdownSerializer.toMarkdown(assignment);
console.log(`Saving assignment ${filePath}`);
await fs.writeFile(filePath, assignmentMarkdown);
},
async getQuiz(courseName: string, moduleName: string, quizName: string) { async getQuiz(courseName: string, moduleName: string, quizName: string) {
const filePath = path.join( const filePath = path.join(
@@ -180,6 +194,19 @@ export const fileStorageService = {
); );
return localPageMarkdownUtils.parseMarkdown(rawFile); return localPageMarkdownUtils.parseMarkdown(rawFile);
}, },
async updatePage(courseName: string, moduleName: string, pageName: string, page: LocalCoursePage) {
const filePath = path.join(
basePath,
courseName,
moduleName,
"pages",
pageName+".md"
);
const pageMarkdown = localPageMarkdownUtils.toMarkdown(page);
console.log(`Saving page ${filePath}`);
await fs.writeFile(filePath, pageMarkdown);
},
async getEmptyDirectories(): Promise<string[]> { async getEmptyDirectories(): Promise<string[]> {
if (!(await directoryOrFileExists(basePath))) { if (!(await directoryOrFileExists(basePath))) {