prevent lectures from dropping on each other

This commit is contained in:
2024-11-02 13:17:36 -06:00
parent b5dc579411
commit 14003d52c9
9 changed files with 212 additions and 67 deletions

View File

@@ -1,20 +1,14 @@
"use client"; "use client";
import { import {
dateToMarkdownString,
getDateFromStringOrThrow, getDateFromStringOrThrow,
getDateOnlyMarkdownString, getDateOnlyMarkdownString,
} from "@/models/local/timeUtils"; } from "@/models/local/timeUtils";
import { useDraggingContext } from "../../context/draggingContext"; import { useDraggingContext } from "../../context/draggingContext";
import { useCourseContext } from "../../context/courseContext";
import Link from "next/link";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { getDayOfWeek } from "@/models/local/localCourse"; import { getDayOfWeek } from "@/models/local/localCourse";
import { getLectureUrl } from "@/services/urlUtils";
import { ItemInDay } from "./ItemInDay"; import { ItemInDay } from "./ItemInDay";
import { useTodaysItems } from "./useTodaysItems"; import { useTodaysItems } from "./useTodaysItems";
import Modal from "@/components/Modal"; import { DayTitle } from "./DayTitle";
import NewItemForm from "../../modules/NewItemForm";
import { useLecturesByWeekQuery } from "@/hooks/localCourse/lectureHooks";
export default function Day({ day, month }: { day: string; month: number }) { export default function Day({ day, month }: { day: string; month: number }) {
const dayAsDate = getDateFromStringOrThrow( const dayAsDate = getDateFromStringOrThrow(
@@ -130,33 +124,3 @@ export default function Day({ day, month }: { day: string; month: number }) {
</div> </div>
); );
} }
function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) {
const { courseName } = useCourseContext();
const { data: weeks } = useLecturesByWeekQuery();
const todaysLecture = weeks
.flatMap((w) => w.lectures)
.find((l) => l.date == getDateOnlyMarkdownString(dayAsDate));
return (
<div className="flex justify-between">
<Link
className="ms-1 me-1 truncate text-nowrap transition-all hover:font-bold hover:text-slate-300"
href={getLectureUrl(courseName, day)}
>
{dayAsDate.getDate()} {todaysLecture?.name}
</Link>
<Modal
buttonText="+"
buttonClass="unstyled hover:font-bold hover:scale-125 px-1 mb-auto mt-0 pt-0"
>
{({ closeModal }) => (
<div>
<NewItemForm creationDate={day} onCreate={closeModal} />
<br />
<button onClick={closeModal}>close</button>
</div>
)}
</Modal>
</div>
);
}

View File

@@ -0,0 +1,53 @@
import Modal from "@/components/Modal";
import { useLecturesByWeekQuery } from "@/hooks/localCourse/lectureHooks";
import { getLectureUrl } from "@/services/urlUtils";
import Link from "next/link";
import { useCourseContext } from "../../context/courseContext";
import NewItemForm from "../../modules/NewItemForm";
import { DraggableItem } from "../../context/draggingContext";
import { useDragStyleContext } from "../../context/dragStyleContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
export function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) {
const { courseName } = useCourseContext();
const { data: weeks } = useLecturesByWeekQuery();
const { setIsDragging } = useDragStyleContext();
const todaysLecture = getLectureForDay(weeks, dayAsDate);
return (
<div className="flex justify-between">
<Link
className="ms-1 me-1 truncate text-nowrap transition-all hover:font-bold hover:text-slate-300"
href={getLectureUrl(courseName, day)}
draggable={true}
onDragStart={(e) => {
if (todaysLecture) {
const draggableItem: DraggableItem = {
type: "lecture",
item: { ...todaysLecture, dueAt: todaysLecture.date },
sourceModuleName: undefined,
};
e.dataTransfer.setData(
"draggableItem",
JSON.stringify(draggableItem)
);
setIsDragging(true);
}
}}
>
{dayAsDate.getDate()} {todaysLecture?.name}
</Link>
<Modal
buttonText="+"
buttonClass="unstyled hover:font-bold hover:scale-125 px-1 mb-auto mt-0 pt-0"
>
{({ closeModal }) => (
<div>
<NewItemForm creationDate={day} onCreate={closeModal} />
<br />
<button onClick={closeModal}>close</button>
</div>
)}
</Modal>
</div>
);
}

View File

@@ -7,12 +7,19 @@ import { LocalQuiz } from "@/models/local/quiz/localQuiz";
import { import {
getDateFromStringOrThrow, getDateFromStringOrThrow,
dateToMarkdownString, dateToMarkdownString,
getDateOnlyMarkdownString,
} from "@/models/local/timeUtils"; } from "@/models/local/timeUtils";
import { LocalAssignment } from "@/models/local/assignment/localAssignment"; import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks"; import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks"; import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks";
import { LocalCoursePage } from "@/models/local/page/localCoursePage"; import { LocalCoursePage } from "@/models/local/page/localCoursePage";
import { useDragStyleContext } from "./dragStyleContext"; import { useDragStyleContext } from "./dragStyleContext";
import { Lecture } from "@/models/local/lecture";
import {
useLecturesByWeekQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import { getLectureForDay } from "@/models/local/lectureUtils";
export default function DraggingContextProvider({ export default function DraggingContextProvider({
children, children,
@@ -21,9 +28,11 @@ export default function DraggingContextProvider({
}) { }) {
const { setIsDragging } = useDragStyleContext(); const { setIsDragging } = useDragStyleContext();
const updateQuizMutation = useUpdateQuizMutation(); const updateQuizMutation = useUpdateQuizMutation();
const updateLectureMutation = useLectureUpdateMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation(); const updateAssignmentMutation = useUpdateAssignmentMutation();
const updatePageMutation = useUpdatePageMutation(); const updatePageMutation = useUpdatePageMutation();
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
const { data: weeks } = useLecturesByWeekQuery();
useEffect(() => { useEffect(() => {
const handleDrop = () => { const handleDrop = () => {
@@ -55,13 +64,16 @@ export default function DraggingContextProvider({
updateAssignment(); updateAssignment();
} else if (itemBeingDragged.type === "page") { } else if (itemBeingDragged.type === "page") {
updatePage(); updatePage();
} else if (itemBeingDragged.type === "lecture") {
// const lecture = itemBeingDragged.item as Lecture & { dueAt: string };
console.log("cannot drop lecture on module, only on days");
} }
} }
setIsDragging(false); setIsDragging(false);
function updateQuiz() { function updateQuiz() {
const quiz = itemBeingDragged.item as LocalQuiz; const quiz = itemBeingDragged.item as LocalQuiz;
if (itemBeingDragged.sourceModuleName) {
updateQuizMutation.mutate({ updateQuizMutation.mutate({
item: quiz, item: quiz,
itemName: quiz.name, itemName: quiz.name,
@@ -69,9 +81,16 @@ export default function DraggingContextProvider({
previousModuleName: itemBeingDragged.sourceModuleName, previousModuleName: itemBeingDragged.sourceModuleName,
previousItemName: quiz.name, previousItemName: quiz.name,
}); });
} else {
console.error(
`error dropping quiz, sourceModuleName is undefined `,
quiz
);
}
} }
function updateAssignment() { function updateAssignment() {
const assignment = itemBeingDragged.item as LocalAssignment; const assignment = itemBeingDragged.item as LocalAssignment;
if (itemBeingDragged.sourceModuleName) {
updateAssignmentMutation.mutate({ updateAssignmentMutation.mutate({
item: assignment, item: assignment,
previousModuleName: itemBeingDragged.sourceModuleName, previousModuleName: itemBeingDragged.sourceModuleName,
@@ -79,9 +98,16 @@ export default function DraggingContextProvider({
itemName: assignment.name, itemName: assignment.name,
previousItemName: assignment.name, previousItemName: assignment.name,
}); });
} else {
console.error(
`error dropping assignment, sourceModuleName is undefined `,
assignment
);
}
} }
function updatePage() { function updatePage() {
const page = itemBeingDragged.item as LocalCoursePage; const page = itemBeingDragged.item as LocalCoursePage;
if (itemBeingDragged.sourceModuleName) {
updatePageMutation.mutate({ updatePageMutation.mutate({
item: page, item: page,
moduleName: dropModuleName, moduleName: dropModuleName,
@@ -89,6 +115,12 @@ export default function DraggingContextProvider({
previousItemName: page.name, previousItemName: page.name,
previousModuleName: itemBeingDragged.sourceModuleName, previousModuleName: itemBeingDragged.sourceModuleName,
}); });
} else {
console.error(
`error dropping page, sourceModuleName is undefined `,
page
);
}
} }
}, },
[ [
@@ -113,6 +145,8 @@ export default function DraggingContextProvider({
updateAssignment(dayAsDate); updateAssignment(dayAsDate);
} else if (itemBeingDragged.type === "page") { } else if (itemBeingDragged.type === "page") {
updatePage(dayAsDate); updatePage(dayAsDate);
} else if (itemBeingDragged.type === "lecture") {
updateLecture(dayAsDate);
} }
} }
setIsDragging(false); setIsDragging(false);
@@ -124,8 +158,32 @@ export default function DraggingContextProvider({
dayAsDate.setSeconds(0); dayAsDate.setSeconds(0);
return dayAsDate; return dayAsDate;
} }
function updateLecture(dayAsDate: Date) {
const { dueAt, ...lecture } = itemBeingDragged.item as Lecture & {
dueAt: string;
};
console.log("dropped lecture on day");
const existingLecture = getLectureForDay(weeks, dayAsDate);
if (existingLecture) {
console.log("attempting to drop on existing lecture");
} else {
updateLectureMutation.mutate({
previousDay: lecture.date,
lecture: {
...lecture,
date: getDateOnlyMarkdownString(dayAsDate),
},
});
}
}
function updateQuiz(dayAsDate: Date) { function updateQuiz(dayAsDate: Date) {
const previousQuiz = itemBeingDragged.item as LocalQuiz; const previousQuiz = itemBeingDragged.item as LocalQuiz;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping quiz on day, sourceModuleName is undefined"
);
return;
}
const quiz: LocalQuiz = { const quiz: LocalQuiz = {
...previousQuiz, ...previousQuiz,
@@ -146,6 +204,12 @@ export default function DraggingContextProvider({
} }
function updatePage(dayAsDate: Date) { function updatePage(dayAsDate: Date) {
const previousPage = itemBeingDragged.item as LocalCoursePage; const previousPage = itemBeingDragged.item as LocalCoursePage;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping page on day, sourceModuleName is undefined"
);
return;
}
const page: LocalCoursePage = { const page: LocalCoursePage = {
...previousPage, ...previousPage,
dueAt: dateToMarkdownString(dayAsDate), dueAt: dateToMarkdownString(dayAsDate),
@@ -159,6 +223,12 @@ export default function DraggingContextProvider({
}); });
} }
function updateAssignment(dayAsDate: Date) { function updateAssignment(dayAsDate: Date) {
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping assignment on day, sourceModuleName is undefined"
);
return;
}
const previousAssignment = itemBeingDragged.item as LocalAssignment; const previousAssignment = itemBeingDragged.item as LocalAssignment;
const assignment: LocalAssignment = { const assignment: LocalAssignment = {
...previousAssignment, ...previousAssignment,
@@ -199,6 +269,7 @@ export default function DraggingContextProvider({
</DraggingContext.Provider> </DraggingContext.Provider>
); );
} }
function getNewLockDate( function getNewLockDate(
originalDueDate: string, originalDueDate: string,
originalLockDate: string | undefined, originalLockDate: string | undefined,

View File

@@ -4,8 +4,8 @@ import { createContext, useContext, DragEvent } from "react";
export interface DraggableItem { export interface DraggableItem {
item: IModuleItem; item: IModuleItem;
sourceModuleName: string; sourceModuleName: string | undefined; // undefined for lectures
type: "quiz" | "assignment" | "page"; type: "quiz" | "assignment" | "page" | "lecture";
} }
export interface DraggingContextInterface { export interface DraggingContextInterface {

View File

@@ -38,7 +38,7 @@ Date: ${lectureDay}
const parsed = parseLecture(text); const parsed = parseLecture(text);
if (!lecture || lectureToString(parsed) !== lectureToString(lecture)) { if (!lecture || lectureToString(parsed) !== lectureToString(lecture)) {
console.log("updating lecture"); console.log("updating lecture");
updateLecture.mutate(parsed); updateLecture.mutate({ lecture: parsed });
} }
setError(""); setError("");
} catch (e: any) { } catch (e: any) {

View File

@@ -5,6 +5,7 @@ import { getDateOnlyMarkdownString } from "@/models/local/timeUtils";
import { getLecturePreviewUrl } from "@/services/urlUtils"; import { getLecturePreviewUrl } from "@/services/urlUtils";
import Link from "next/link"; import Link from "next/link";
import { useCourseContext } from "../course/[courseName]/context/courseContext"; import { useCourseContext } from "../course/[courseName]/context/courseContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
export default function OneCourseLectures() { export default function OneCourseLectures() {
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
@@ -12,9 +13,7 @@ export default function OneCourseLectures() {
const dayAsDate = new Date(); const dayAsDate = new Date();
const dayAsString = getDateOnlyMarkdownString(dayAsDate); const dayAsString = getDateOnlyMarkdownString(dayAsDate);
const todaysLecture = weeks const todaysLecture = getLectureForDay(weeks, dayAsDate);
.flatMap((w) => w.lectures)
.find((l) => l.date == dayAsString);
if (!todaysLecture) return <></>; if (!todaysLecture) return <></>;
return ( return (
<Link <Link

View File

@@ -6,6 +6,7 @@ import {
import { lectureKeys } from "./lectureKeys"; import { lectureKeys } from "./lectureKeys";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
import { import {
deleteLecture,
getLectures, getLectures,
updateLecture, updateLecture,
} from "@/services/fileStorage/lectureFileStorageService"; } from "@/services/fileStorage/lectureFileStorageService";
@@ -28,8 +29,18 @@ export const useLectureUpdateMutation = () => {
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (lecture: Lecture) => { mutationFn: async ({
lecture,
previousDay,
}: {
lecture: Lecture;
previousDay?: string;
}) => {
await updateLecture(courseName, settings, lecture); await updateLecture(courseName, settings, lecture);
if (previousDay && previousDay !== lecture.date) {
await deleteLecture(courseName, settings, previousDay);
}
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({

View File

@@ -0,0 +1,9 @@
import { Lecture } from "./lecture";
import { getDateOnlyMarkdownString } from "./timeUtils";
export function getLectureForDay(weeks: { weekName: string; lectures: Lecture[]; }[], dayAsDate: Date) {
return weeks
.flatMap((w) => w.lectures)
.find((l) => l.date == getDateOnlyMarkdownString(dayAsDate));
}

View File

@@ -57,7 +57,10 @@ export async function updateLecture(
"lecture start date in update lecture" "lecture start date in update lecture"
); );
const weekFolderName = getLectureWeekName(courseSettings.startDate, lecture.date); const weekFolderName = getLectureWeekName(
courseSettings.startDate,
lecture.date
);
const weekPath = path.join(courseLectureRoot, weekFolderName); const weekPath = path.join(courseLectureRoot, weekFolderName);
if (!(await directoryExists(weekPath))) { if (!(await directoryExists(weekPath))) {
await fs.mkdir(weekPath, { recursive: true }); await fs.mkdir(weekPath, { recursive: true });
@@ -71,6 +74,41 @@ export async function updateLecture(
await fs.writeFile(lecturePath, lectureContents); await fs.writeFile(lecturePath, lectureContents);
} }
export async function deleteLecture(
courseName: string,
courseSettings: LocalCourseSettings,
dayAsString: string
) {
console.log("deleting lecture", courseName, dayAsString);
const lectureDate = getDateFromStringOrThrow(
dayAsString,
"lecture start date in update lecture"
);
const weekFolderName = getLectureWeekName(
courseSettings.startDate,
dayAsString
);
const courseLectureRoot = path.join(basePath, courseName, lectureFolderName);
const weekPath = path.join(courseLectureRoot, weekFolderName);
const lecturePath = path.join(
weekPath,
`${lectureDate.getDay()}-${getDayOfWeek(lectureDate)}.md`
);
try {
await fs.access(lecturePath); // throws error if no file
await fs.unlink(lecturePath);
console.log(`File deleted: ${lecturePath}`);
} catch (error: any) {
if (error?.code === "ENOENT") {
console.log(`Cannot delete lecture, file does not exist: ${lecturePath}`);
} else {
throw error;
}
}
}
const directoryExists = async (path: string): Promise<boolean> => { const directoryExists = async (path: string): Promise<boolean> => {
try { try {
const stat = await fs.stat(path); const stat = await fs.stat(path);