refactored drag provider

This commit is contained in:
2024-11-02 13:59:25 -06:00
parent 51e0b088bb
commit fd65bf710d
4 changed files with 372 additions and 309 deletions

View File

@@ -1,27 +1,10 @@
"use client"; "use client";
import { ReactNode, useCallback, DragEvent, useEffect, useState } from "react"; import { ReactNode, useEffect, useState } from "react";
import { DraggableItem, DraggingContext } from "./draggingContext"; import { DraggingContext } from "./draggingContext";
import { useUpdateQuizMutation } from "@/hooks/localCourse/quizHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { LocalQuiz } from "@/models/local/quiz/localQuiz";
import {
getDateFromStringOrThrow,
dateToMarkdownString,
getDateOnlyMarkdownString,
} 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";
import { useDragStyleContext } from "./dragStyleContext"; import { useDragStyleContext } from "./dragStyleContext";
import { Lecture } from "@/models/local/lecture"; import { useModal } from "@/components/Modal";
import { import { LectureReplaceModal } from "./LectureReplaceModal";
useLecturesByWeekQuery, import { useItemDropOnDay, useItemDropOnModule } from "./draggingContextUtils";
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import { getLectureForDay } from "@/models/local/lectureUtils";
import Modal, { useModal } from "@/components/Modal";
import { Spinner } from "@/components/Spinner";
export default function DraggingContextProvider({ export default function DraggingContextProvider({
children, children,
@@ -29,12 +12,6 @@ export default function DraggingContextProvider({
children: ReactNode; children: ReactNode;
}) { }) {
const { setIsDragging } = useDragStyleContext(); const { setIsDragging } = useDragStyleContext();
const updateQuizMutation = useUpdateQuizMutation();
const updateLectureMutation = useLectureUpdateMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();
const updatePageMutation = useUpdatePageMutation();
const { data: settings } = useLocalCourseSettingsQuery();
const { data: weeks } = useLecturesByWeekQuery();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [modalText, setModalText] = useState(""); const [modalText, setModalText] = useState("");
const modal = useModal(); const modal = useModal();
@@ -56,237 +33,17 @@ export default function DraggingContextProvider({
}; };
}, [setIsDragging]); }, [setIsDragging]);
const itemDropOnModule = useCallback( const itemDropOnModule = useItemDropOnModule({
(e: DragEvent<HTMLDivElement>, dropModuleName: string) => { setIsDragging,
console.log("dropping on module"); });
const rawData = e.dataTransfer.getData("draggableItem");
if (!rawData) return;
const itemBeingDragged: DraggableItem = JSON.parse(rawData);
if (itemBeingDragged) { const itemDropOnDay = useItemDropOnDay({
if (itemBeingDragged.type === "quiz") { setIsDragging,
updateQuiz(); setModalText,
} else if (itemBeingDragged.type === "assignment") { setModalCallback,
updateAssignment(); setIsLoading,
} else if (itemBeingDragged.type === "page") { modal,
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);
function updateQuiz() {
const quiz = itemBeingDragged.item as LocalQuiz;
if (itemBeingDragged.sourceModuleName) {
updateQuizMutation.mutate({
item: quiz,
itemName: quiz.name,
moduleName: dropModuleName,
previousModuleName: itemBeingDragged.sourceModuleName,
previousItemName: quiz.name,
});
} else {
console.error(
`error dropping quiz, sourceModuleName is undefined `,
quiz
);
}
}
function updateAssignment() {
const assignment = itemBeingDragged.item as LocalAssignment;
if (itemBeingDragged.sourceModuleName) {
updateAssignmentMutation.mutate({
item: assignment,
previousModuleName: itemBeingDragged.sourceModuleName,
moduleName: dropModuleName,
itemName: assignment.name,
previousItemName: assignment.name,
});
} else {
console.error(
`error dropping assignment, sourceModuleName is undefined `,
assignment
);
}
}
function updatePage() {
const page = itemBeingDragged.item as LocalCoursePage;
if (itemBeingDragged.sourceModuleName) {
updatePageMutation.mutate({
item: page,
moduleName: dropModuleName,
itemName: page.name,
previousItemName: page.name,
previousModuleName: itemBeingDragged.sourceModuleName,
});
} else {
console.error(
`error dropping page, sourceModuleName is undefined `,
page
);
}
}
},
[
setIsDragging,
updateAssignmentMutation,
updatePageMutation,
updateQuizMutation,
]
);
const itemDropOnDay = useCallback(
(e: DragEvent<HTMLDivElement>, day: string) => {
const rawData = e.dataTransfer.getData("draggableItem");
if (!rawData) return;
const itemBeingDragged: DraggableItem = JSON.parse(rawData);
if (itemBeingDragged) {
const dayAsDate = getDateWithDefaultDueTime();
if (itemBeingDragged.type === "quiz") {
updateQuiz(dayAsDate);
} else if (itemBeingDragged.type === "assignment") {
updateAssignment(dayAsDate);
} else if (itemBeingDragged.type === "page") {
updatePage(dayAsDate);
} else if (itemBeingDragged.type === "lecture") {
updateLecture(dayAsDate);
}
}
setIsDragging(false);
function getDateWithDefaultDueTime() {
const dayAsDate = getDateFromStringOrThrow(day, "in drop callback");
dayAsDate.setHours(settings.defaultDueTime.hour);
dayAsDate.setMinutes(settings.defaultDueTime.minute);
dayAsDate.setSeconds(0);
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");
setModalText(
`Are you sure you want to replace ${existingLecture?.name} with ${lecture.name}? This will delete ${existingLecture.name}.`
);
setModalCallback(() => async () => { // because sometimes setStates receive a function
console.log("running callback");
setIsLoading(true);
await updateLectureMutation.mutateAsync({
previousDay: lecture.date,
lecture: {
...lecture,
date: getDateOnlyMarkdownString(dayAsDate),
},
});
setModalText("");
setModalCallback(() => {});
modal.closeModal();
setIsLoading(false);
});
modal.openModal();
} else {
console.log("updating lecture on unique day");
updateLectureMutation.mutate({
previousDay: lecture.date,
lecture: {
...lecture,
date: getDateOnlyMarkdownString(dayAsDate),
},
});
}
}
function updateQuiz(dayAsDate: Date) {
const previousQuiz = itemBeingDragged.item as LocalQuiz;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping quiz on day, sourceModuleName is undefined"
);
return;
}
const quiz: LocalQuiz = {
...previousQuiz,
dueAt: dateToMarkdownString(dayAsDate),
lockAt: getNewLockDate(
previousQuiz.dueAt,
previousQuiz.lockAt,
dayAsDate
),
};
updateQuizMutation.mutate({
item: quiz,
itemName: quiz.name,
moduleName: itemBeingDragged.sourceModuleName,
previousModuleName: itemBeingDragged.sourceModuleName,
previousItemName: quiz.name,
});
}
function updatePage(dayAsDate: Date) {
const previousPage = itemBeingDragged.item as LocalCoursePage;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping page on day, sourceModuleName is undefined"
);
return;
}
const page: LocalCoursePage = {
...previousPage,
dueAt: dateToMarkdownString(dayAsDate),
};
updatePageMutation.mutate({
item: page,
moduleName: itemBeingDragged.sourceModuleName,
itemName: page.name,
previousItemName: page.name,
previousModuleName: itemBeingDragged.sourceModuleName,
});
}
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 assignment: LocalAssignment = {
...previousAssignment,
dueAt: dateToMarkdownString(dayAsDate),
lockAt: getNewLockDate(
previousAssignment.dueAt,
previousAssignment.lockAt,
dayAsDate
),
};
updateAssignmentMutation.mutate({
item: assignment,
previousModuleName: itemBeingDragged.sourceModuleName,
moduleName: itemBeingDragged.sourceModuleName,
itemName: assignment.name,
previousItemName: assignment.name,
});
}
},
[
modal,
setIsDragging,
settings.defaultDueTime.hour,
settings.defaultDueTime.minute,
updateAssignmentMutation,
updateLectureMutation,
updatePageMutation,
updateQuizMutation,
weeks,
]
);
return ( return (
<DraggingContext.Provider <DraggingContext.Provider
@@ -295,56 +52,13 @@ export default function DraggingContextProvider({
itemDropOnModule, itemDropOnModule,
}} }}
> >
<Modal modalControl={modal} buttonText={""} buttonClass="hidden"> <LectureReplaceModal
{({ closeModal }) => ( modal={modal}
<div> modalText={modalText}
<div className="text-center">{modalText}</div> modalCallback={modalCallback}
<br /> isLoading={isLoading}
<div className="flex justify-around gap-3"> />
<button
onClick={() => {
console.log("deleting");
modalCallback();
}}
disabled={isLoading}
className="btn-danger"
>
Yes
</button>
<button onClick={closeModal} disabled={isLoading}>
No
</button>
</div>
{isLoading && <Spinner />}
</div>
)}
</Modal>
{children} {children}
</DraggingContext.Provider> </DraggingContext.Provider>
); );
} }
function getNewLockDate(
originalDueDate: string,
originalLockDate: string | undefined,
dayAsDate: Date
): string | undefined {
// todo: preserve previous due date / lock date offset
const dueDate = getDateFromStringOrThrow(originalDueDate, "dueAt date");
const lockDate =
originalLockDate === undefined
? undefined
: getDateFromStringOrThrow(originalLockDate, "lockAt date");
const originalOffset =
lockDate === undefined ? undefined : lockDate.getTime() - dueDate.getTime();
const newLockDate =
originalOffset === undefined
? undefined
: new Date(dayAsDate.getTime() + originalOffset);
return newLockDate === undefined
? undefined
: dateToMarkdownString(newLockDate);
}

View File

@@ -0,0 +1,39 @@
"use client";
import Modal, { ModalControl } from "@/components/Modal";
import { Spinner } from "@/components/Spinner";
export function LectureReplaceModal({
modal, modalText, modalCallback, isLoading,
}: {
modal: ModalControl;
modalText: string;
modalCallback: () => void;
isLoading: boolean;
}) {
return (
<Modal modalControl={modal} buttonText={""} buttonClass="hidden">
{({ closeModal }) => (
<div>
<div className="text-center">{modalText}</div>
<br />
<div className="flex justify-around gap-3">
<button
onClick={() => {
console.log("deleting");
modalCallback();
}}
disabled={isLoading}
className="btn-danger"
>
Yes
</button>
<button onClick={closeModal} disabled={isLoading}>
No
</button>
</div>
{isLoading && <Spinner />}
</div>
)}
</Modal>
);
}

View File

@@ -9,8 +9,8 @@ export interface DraggableItem {
} }
export interface DraggingContextInterface { export interface DraggingContextInterface {
itemDropOnDay: (e: DragEvent<HTMLDivElement>, droppedOnDay: string) => void; itemDropOnDay: (e: DragEvent, droppedOnDay: string) => void;
itemDropOnModule: (e: DragEvent<HTMLDivElement>, moduleName: string) => void; itemDropOnModule: (e: DragEvent, moduleName: string) => void;
} }
const defaultDraggingValue: DraggingContextInterface = { const defaultDraggingValue: DraggingContextInterface = {
itemDropOnDay: () => {}, itemDropOnDay: () => {},

View File

@@ -0,0 +1,310 @@
"use client";
import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
import {
useLecturesByWeekQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks";
import { useUpdateQuizMutation } from "@/hooks/localCourse/quizHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { Lecture } from "@/models/local/lecture";
import { getLectureForDay } from "@/models/local/lectureUtils";
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
import { LocalQuiz } from "@/models/local/quiz/localQuiz";
import {
getDateFromStringOrThrow,
getDateOnlyMarkdownString,
dateToMarkdownString,
} from "@/models/local/timeUtils";
import { Dispatch, SetStateAction, useCallback, DragEvent } from "react";
import { DraggableItem } from "./draggingContext";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
export function useItemDropOnDay({
setIsDragging,
setModalText,
setModalCallback,
setIsLoading,
modal,
}: {
setIsDragging: Dispatch<SetStateAction<boolean>>;
setModalText: Dispatch<SetStateAction<string>>;
setModalCallback: Dispatch<SetStateAction<() => void>>;
setIsLoading: Dispatch<SetStateAction<boolean>>;
modal: { isOpen: boolean; openModal: () => void; closeModal: () => void };
}) {
const { data: settings } = useLocalCourseSettingsQuery();
const { data: weeks } = useLecturesByWeekQuery();
const updateQuizMutation = useUpdateQuizMutation();
const updateLectureMutation = useLectureUpdateMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();
const updatePageMutation = useUpdatePageMutation();
return useCallback(
(e: DragEvent, day: string) => {
const rawData = e.dataTransfer.getData("draggableItem");
if (!rawData) return;
const itemBeingDragged: DraggableItem = JSON.parse(rawData);
if (itemBeingDragged) {
const dayAsDate = getDateWithDefaultDueTime();
if (itemBeingDragged.type === "quiz") {
updateQuiz(dayAsDate);
} else if (itemBeingDragged.type === "assignment") {
updateAssignment(dayAsDate);
} else if (itemBeingDragged.type === "page") {
updatePage(dayAsDate);
} else if (itemBeingDragged.type === "lecture") {
updateLecture(dayAsDate);
}
}
setIsDragging(false);
function getDateWithDefaultDueTime() {
const dayAsDate = getDateFromStringOrThrow(day, "in drop callback");
dayAsDate.setHours(settings.defaultDueTime.hour);
dayAsDate.setMinutes(settings.defaultDueTime.minute);
dayAsDate.setSeconds(0);
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");
setModalText(
`Are you sure you want to replace ${existingLecture?.name} with ${lecture.name}? This will delete ${existingLecture.name}.`
);
setModalCallback(() => async () => {
// because sometimes setStates receive a function
console.log("running callback");
setIsLoading(true);
await updateLectureMutation.mutateAsync({
previousDay: lecture.date,
lecture: {
...lecture,
date: getDateOnlyMarkdownString(dayAsDate),
},
});
setModalText("");
setModalCallback(() => {});
modal.closeModal();
setIsLoading(false);
});
modal.openModal();
} else {
console.log("updating lecture on unique day");
updateLectureMutation.mutate({
previousDay: lecture.date,
lecture: {
...lecture,
date: getDateOnlyMarkdownString(dayAsDate),
},
});
}
}
function updateQuiz(dayAsDate: Date) {
const previousQuiz = itemBeingDragged.item as LocalQuiz;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping quiz on day, sourceModuleName is undefined"
);
return;
}
const quiz: LocalQuiz = {
...previousQuiz,
dueAt: dateToMarkdownString(dayAsDate),
lockAt: getNewLockDate(
previousQuiz.dueAt,
previousQuiz.lockAt,
dayAsDate
),
};
updateQuizMutation.mutate({
item: quiz,
itemName: quiz.name,
moduleName: itemBeingDragged.sourceModuleName,
previousModuleName: itemBeingDragged.sourceModuleName,
previousItemName: quiz.name,
});
}
function updatePage(dayAsDate: Date) {
const previousPage = itemBeingDragged.item as LocalCoursePage;
if (!itemBeingDragged.sourceModuleName) {
console.error(
"error dropping page on day, sourceModuleName is undefined"
);
return;
}
const page: LocalCoursePage = {
...previousPage,
dueAt: dateToMarkdownString(dayAsDate),
};
updatePageMutation.mutate({
item: page,
moduleName: itemBeingDragged.sourceModuleName,
itemName: page.name,
previousItemName: page.name,
previousModuleName: itemBeingDragged.sourceModuleName,
});
}
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 assignment: LocalAssignment = {
...previousAssignment,
dueAt: dateToMarkdownString(dayAsDate),
lockAt: getNewLockDate(
previousAssignment.dueAt,
previousAssignment.lockAt,
dayAsDate
),
};
updateAssignmentMutation.mutate({
item: assignment,
previousModuleName: itemBeingDragged.sourceModuleName,
moduleName: itemBeingDragged.sourceModuleName,
itemName: assignment.name,
previousItemName: assignment.name,
});
}
},
[
modal,
setIsDragging,
setIsLoading,
setModalCallback,
setModalText,
settings.defaultDueTime.hour,
settings.defaultDueTime.minute,
updateAssignmentMutation,
updateLectureMutation,
updatePageMutation,
updateQuizMutation,
weeks,
]
);
}
export function useItemDropOnModule({
setIsDragging,
}: {
setIsDragging: Dispatch<SetStateAction<boolean>>;
}) {
const updateQuizMutation = useUpdateQuizMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();
const updatePageMutation = useUpdatePageMutation();
return useCallback(
(e: DragEvent, dropModuleName: string) => {
console.log("dropping on module");
const rawData = e.dataTransfer.getData("draggableItem");
if (!rawData) return;
const itemBeingDragged: DraggableItem = JSON.parse(rawData);
if (itemBeingDragged) {
if (itemBeingDragged.type === "quiz") {
updateQuiz();
} else if (itemBeingDragged.type === "assignment") {
updateAssignment();
} else if (itemBeingDragged.type === "page") {
updatePage();
} else if (itemBeingDragged.type === "lecture") {
console.log("cannot drop lecture on module, only on days");
}
}
setIsDragging(false);
function updateQuiz() {
const quiz = itemBeingDragged.item as LocalQuiz;
if (itemBeingDragged.sourceModuleName) {
updateQuizMutation.mutate({
item: quiz,
itemName: quiz.name,
moduleName: dropModuleName,
previousModuleName: itemBeingDragged.sourceModuleName,
previousItemName: quiz.name,
});
} else {
console.error(
`error dropping quiz, sourceModuleName is undefined `,
quiz
);
}
}
function updateAssignment() {
const assignment = itemBeingDragged.item as LocalAssignment;
if (itemBeingDragged.sourceModuleName) {
updateAssignmentMutation.mutate({
item: assignment,
previousModuleName: itemBeingDragged.sourceModuleName,
moduleName: dropModuleName,
itemName: assignment.name,
previousItemName: assignment.name,
});
} else {
console.error(
`error dropping assignment, sourceModuleName is undefined `,
assignment
);
}
}
function updatePage() {
const page = itemBeingDragged.item as LocalCoursePage;
if (itemBeingDragged.sourceModuleName) {
updatePageMutation.mutate({
item: page,
moduleName: dropModuleName,
itemName: page.name,
previousItemName: page.name,
previousModuleName: itemBeingDragged.sourceModuleName,
});
} else {
console.error(
`error dropping page, sourceModuleName is undefined `,
page
);
}
}
},
[
setIsDragging,
updateAssignmentMutation,
updatePageMutation,
updateQuizMutation,
]
);
}
export function getNewLockDate(
originalDueDate: string,
originalLockDate: string | undefined,
dayAsDate: Date
): string | undefined {
// todo: preserve previous due date / lock date offset
const dueDate = getDateFromStringOrThrow(originalDueDate, "dueAt date");
const lockDate =
originalLockDate === undefined
? undefined
: getDateFromStringOrThrow(originalLockDate, "lockAt date");
const originalOffset =
lockDate === undefined ? undefined : lockDate.getTime() - dueDate.getTime();
const newLockDate =
originalOffset === undefined
? undefined
: new Date(dayAsDate.getTime() + originalOffset);
return newLockDate === undefined
? undefined
: dateToMarkdownString(newLockDate);
}