From da7cd1b2383655948880211b6eca08eee2999019 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Mon, 23 Sep 2024 17:32:39 -0600 Subject: [PATCH] working on context menu --- .../assignments/[assignmentName]/route.ts | 18 +++++ .../[moduleName]/pages/[pageName]/route.ts | 15 ++++ .../[moduleName]/quizzes/[quizName]/route.ts | 18 ++++- .../course/[courseName]/calendar/day/Day.tsx | 14 ++++ .../calendar/day/DayContextMenu.tsx | 70 +++++++++++++++++++ .../[courseName]/modules/NewItemForm.tsx | 58 +++++++++++++-- .../page/[pageName]/EditPageButtons.tsx | 39 ++++++++++- .../[moduleName]/quiz/[quizName]/EditQuiz.tsx | 2 +- .../quiz/[quizName]/QuizButton.tsx | 38 +++++++++- nextjs/src/components/Modal.tsx | 5 +- nextjs/src/hooks/localCourse/pageHooks.ts | 44 +++++++++++- nextjs/src/hooks/localCourse/quizHooks.ts | 34 +++++++++ 12 files changed, 341 insertions(+), 14 deletions(-) create mode 100644 nextjs/src/app/course/[courseName]/calendar/day/DayContextMenu.tsx diff --git a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts index 6d773a2..2da1104 100644 --- a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts +++ b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts @@ -74,3 +74,21 @@ export const POST = async ( }); return Response.json({}); }); + +export const DELETE = async ( + _request: Request, + { + params: { courseName, moduleName, assignmentName }, + }: { + params: { courseName: string; moduleName: string; assignmentName: string }; + } +) => + await withErrorHandling(async () => { + fileStorageService.assignments.delete({ + courseName, + moduleName, + assignmentName, + }); + + return Response.json({}); + }); diff --git a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts index 9544ee3..f48d497 100644 --- a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts +++ b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts @@ -61,3 +61,18 @@ export const POST = async ( ); return Response.json({}); }); + +export const DELETE = async ( + _request: Request, + { + params: { courseName, moduleName, pageName }, + }: { params: { courseName: string; moduleName: string; pageName: string } } +) => + await withErrorHandling(async () => { + fileStorageService.pages.delete({ + courseName, + moduleName, + pageName, + }); + return Response.json({}); + }); diff --git a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/quizzes/[quizName]/route.ts b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/quizzes/[quizName]/route.ts index 4d3e5e9..cc3a70d 100644 --- a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/quizzes/[quizName]/route.ts +++ b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/quizzes/[quizName]/route.ts @@ -40,8 +40,7 @@ export const PUT = async ( if ( previousModuleName && previousQuizName && - (quiz.name !== previousQuizName || - moduleName !== previousModuleName) + (quiz.name !== previousQuizName || moduleName !== previousModuleName) ) { fileStorageService.quizzes.delete({ courseName, @@ -68,3 +67,18 @@ export const POST = async ( ); return Response.json({}); }); +export const DELETE = async ( + _request: Request, + { + params: { courseName, moduleName, quizName }, + }: { params: { courseName: string; moduleName: string; quizName: string } } +) => + await withErrorHandling(async () => { + fileStorageService.quizzes.delete({ + courseName, + moduleName, + quizName, + }); + + return Response.json({}); + }); diff --git a/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx b/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx index f098f12..3a48bb0 100644 --- a/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx +++ b/nextjs/src/app/course/[courseName]/calendar/day/Day.tsx @@ -12,6 +12,8 @@ import { getLectureUrl } from "@/services/urlUtils"; import DropTargetStyling from "../../../../../components/DropTargetStyling"; import { ItemInDay } from "./ItemInDay"; import { useTodaysItems } from "./useTodaysItems"; +import { useState } from "react"; +import { DayContextMenu } from "./DayContextMenu"; export default function Day({ day, month }: { day: string; month: number }) { const dayAsDate = getDateFromStringOrThrow( @@ -24,6 +26,9 @@ export default function Day({ day, month }: { day: string; month: number }) { const { data: settings } = useLocalCourseSettingsQuery(); const { itemDropOnDay } = useDraggingContext(); + const [contextCoordinates, setContextCoordinates] = useState< + { x: number; y: number } | undefined + >(); const { todaysAssignments, todaysQuizzes, todaysPages } = useTodaysItems(day); @@ -43,8 +48,17 @@ export default function Day({ day, month }: { day: string; month: number }) { className={" rounded-lg m-1 min-h-10 " + meetingClasses + monthClass} onDrop={(e) => itemDropOnDay(e, day)} onDragOver={(e) => e.preventDefault()} + onContextMenu={(e) => { + e.preventDefault(); + setContextCoordinates({ x: e.pageX, y: e.pageY }); + }} > + setContextCoordinates(undefined)} + />
{todaysAssignments.map( diff --git a/nextjs/src/app/course/[courseName]/calendar/day/DayContextMenu.tsx b/nextjs/src/app/course/[courseName]/calendar/day/DayContextMenu.tsx new file mode 100644 index 0000000..7afc527 --- /dev/null +++ b/nextjs/src/app/course/[courseName]/calendar/day/DayContextMenu.tsx @@ -0,0 +1,70 @@ +import Modal from "@/components/Modal"; +import React, { FC, useEffect, useRef } from "react"; +import NewItemForm from "../../modules/NewItemForm"; + +export const DayContextMenu: FC<{ + coordinates?: { x: number; y: number }; + hideContextMenu: () => void; + day: string; +}> = ({ coordinates, hideContextMenu, day }) => { + const menuRef = useRef(null); + + // const handleContextMenu = (event: MouseEvent) => { + // event.preventDefault(); + // setPosition({ x: event.pageX, y: event.pageY }); + // setVisible(true); + // }; + + const handleClick = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + hideContextMenu(); + } + }; + + useEffect(() => { + document.addEventListener("click", handleClick); + + return () => { + document.removeEventListener("click", handleClick); + }; + }, []); + + return ( +
{ + console.log(e.target); + e.stopPropagation(); + hideContextMenu(); + }} + ref={menuRef} + > + + {({ closeModal }) => ( +
+ { + closeModal(); + hideContextMenu(); + }} + /> +
+ +
+ )} +
+
+ ); +}; diff --git a/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx b/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx index 931a764..77031b7 100644 --- a/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx +++ b/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx @@ -1,30 +1,55 @@ "use client"; import ButtonSelect from "@/components/ButtonSelect"; +import SelectInput from "@/components/form/SelectInput"; import TextInput from "@/components/form/TextInput"; import { Spinner } from "@/components/Spinner"; import { useCreateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks"; +import { useModuleNamesQuery } from "@/hooks/localCourse/localCourseModuleHooks"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { useCreatePageMutation } from "@/hooks/localCourse/pageHooks"; import { useCreateQuizMutation } from "@/hooks/localCourse/quizHooks"; import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType"; import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup"; -import { dateToMarkdownString } from "@/models/local/timeUtils"; +import { + dateToMarkdownString, + getDateFromString, +} from "@/models/local/timeUtils"; import React, { useState } from "react"; export default function NewItemForm({ - moduleName, + moduleName: defaultModuleName, onCreate = () => {}, + creationDate, }: { - moduleName: string; + moduleName?: string; + creationDate?: string; onCreate?: () => void; }) { + const { data: settings } = useLocalCourseSettingsQuery(); + const { data: modules } = useModuleNamesQuery(); const [type, setType] = useState<"Assignment" | "Quiz" | "Page">( "Assignment" ); + + const [moduleName, setModuleName] = useState( + defaultModuleName + ); + const [name, setName] = useState(""); + + const defaultDate = getDateFromString( + creationDate ? creationDate : dateToMarkdownString(new Date()) + ); + defaultDate?.setMinutes(settings.defaultDueTime.minute); + defaultDate?.setHours(settings.defaultDueTime.hour); + defaultDate?.setSeconds(0); + + const [dueDate, setDueDate] = useState( + dateToMarkdownString(defaultDate ?? new Date()) + ); const [assignmentGroup, setAssignmentGroup] = useState(); - const { data: settings } = useLocalCourseSettingsQuery(); + const createAssignment = useCreateAssignmentMutation(); const createPage = useCreatePageMutation(); const createQuiz = useCreateQuizMutation(); @@ -37,8 +62,15 @@ export default function NewItemForm({ className="flex flex-col gap-3" onSubmit={(e) => { e.preventDefault(); - const dueAt = dateToMarkdownString(new Date()); + const dueAt = + dueDate === "" + ? dueDate + : dateToMarkdownString(defaultDate ?? new Date()); + console.log("submitting"); + if (!moduleName) { + return; + } if (type === "Assignment") { createAssignment.mutate({ assignment: { @@ -84,6 +116,22 @@ export default function NewItemForm({ onCreate(); }} > +
+ +
+
+ setModuleName(m)} + label={"Module"} + options={modules} + getOptionName={(m) => m} + /> +
options={["Assignment", "Quiz", "Page"]} diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx index 1e71ba7..51fe105 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPageButtons.tsx @@ -1,4 +1,5 @@ import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; +import Modal from "@/components/Modal"; import { Spinner } from "@/components/Spinner"; import { useCanvasPagesQuery, @@ -7,10 +8,14 @@ import { useUpdateCanvasPageMutation, } from "@/hooks/canvas/canvasPageHooks"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; -import { usePageQuery } from "@/hooks/localCourse/pageHooks"; +import { + useDeletePageMutation, + usePageQuery, +} from "@/hooks/localCourse/pageHooks"; import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils"; import { getCourseUrl } from "@/services/urlUtils"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import React from "react"; export default function EditPageButtons({ @@ -20,6 +25,7 @@ export default function EditPageButtons({ pageName: string; moduleName: string; }) { + const router = useRouter(); const { courseName } = useCourseContext(); const { data: settings } = useLocalCourseSettingsQuery(); const { data: page } = usePageQuery(moduleName, pageName); @@ -27,6 +33,7 @@ export default function EditPageButtons({ const createPageInCanvas = useCreateCanvasPageMutation(); const updatePageInCanvas = useUpdateCanvasPageMutation(); const deletePageInCanvas = useDeleteCanvasPageMutation(); + const deletePageLocal = useDeletePageMutation(); const pageInCanvas = canvasPages?.find((p) => p.title === pageName); @@ -77,6 +84,36 @@ export default function EditPageButtons({ Delete from Canvas )} + + {!pageInCanvas && ( + + {({ closeModal }) => ( +
+
+ Are you sure you want to delete this page locally? +
+
+
+ + +
+
+ )} +
+ )} Go Back diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx index 764ed8c..ca66f54 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/EditQuiz.tsx @@ -115,7 +115,7 @@ export default function EditQuiz({ return (
-
+
{showHelp && (
             {helpString}
diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx
index 82e6eaf..42d3de1 100644
--- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx
+++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizButton.tsx
@@ -1,4 +1,5 @@
 import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
+import Modal from "@/components/Modal";
 import { Spinner } from "@/components/Spinner";
 import {
   useCanvasQuizzesQuery,
@@ -6,10 +7,14 @@ import {
   useDeleteQuizFromCanvasMutation,
 } from "@/hooks/canvas/canvasQuizHooks";
 import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
-import { useQuizQuery } from "@/hooks/localCourse/quizHooks";
+import {
+  useDeleteQuizMutation,
+  useQuizQuery,
+} from "@/hooks/localCourse/quizHooks";
 import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
 import { getCourseUrl } from "@/services/urlUtils";
 import Link from "next/link";
+import { useRouter } from "next/navigation";
 
 export function QuizButtons({
   moduleName,
@@ -20,12 +25,14 @@ export function QuizButtons({
   moduleName: string;
   toggleHelp: () => void;
 }) {
+  const router = useRouter();
   const { courseName } = useCourseContext();
   const { data: settings } = useLocalCourseSettingsQuery();
   const { data: canvasQuizzes } = useCanvasQuizzesQuery();
   const { data: quiz } = useQuizQuery(moduleName, quizName);
   const addToCanvas = useAddQuizToCanvasMutation();
   const deleteFromCanvas = useDeleteQuizFromCanvasMutation();
+  const deleteLocal = useDeleteQuizMutation();
 
   const quizInCanvas = canvasQuizzes.find((c) => c.title === quizName);
 
@@ -65,6 +72,35 @@ export function QuizButtons({
             Delete from Canvas
           
         )}
+        {!quizInCanvas && (
+          
+            {({ closeModal }) => (
+              
+
+ Are you sure you want to delete this quiz locally? +
+
+
+ + +
+
+ )} +
+ )} Go Back diff --git a/nextjs/src/components/Modal.tsx b/nextjs/src/components/Modal.tsx index 78e4409..7bac7a6 100644 --- a/nextjs/src/components/Modal.tsx +++ b/nextjs/src/components/Modal.tsx @@ -5,10 +5,12 @@ export default function Modal({ children, buttonText, buttonClass = "", + modalWidth = "w-1/3", }: { children: (props: { closeModal: () => void }) => ReactNode; buttonText: string; buttonClass?: string; + modalWidth?: string; }) { const [isOpen, setIsOpen] = useState(false); @@ -35,7 +37,8 @@ export default function Modal({ e.stopPropagation(); }} className={ - ` bg-slate-800 p-6 rounded-lg shadow-lg w-1/3 ` + + ` bg-slate-800 p-6 rounded-lg shadow-lg ` + + modalWidth + ` transition-all duration-400 ` + ` ${isOpen ? "opacity-100" : "opacity-0"}` } diff --git a/nextjs/src/hooks/localCourse/pageHooks.ts b/nextjs/src/hooks/localCourse/pageHooks.ts index 51e6898..d101225 100644 --- a/nextjs/src/hooks/localCourse/pageHooks.ts +++ b/nextjs/src/hooks/localCourse/pageHooks.ts @@ -133,9 +133,9 @@ export const useUpdatePageMutation = () => { queryClient.invalidateQueries({ queryKey: localCourseKeys.page(courseName, moduleName, pageName), }); - // queryClient.invalidateQueries({ - // queryKey: localCourseKeys.pageNames(courseName, moduleName), - // }); + queryClient.invalidateQueries({ + queryKey: localCourseKeys.pageNames(courseName, moduleName), + }); }, }); }; @@ -176,3 +176,41 @@ export const useCreatePageMutation = () => { }, }); }; + +export const useDeletePageMutation = () => { + const { courseName } = useCourseContext(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + moduleName, + pageName, + }: { + moduleName: string; + pageName: string; + }) => { + queryClient.removeQueries({ + queryKey: localCourseKeys.page(courseName, moduleName, pageName), + }); + queryClient.removeQueries({ + queryKey: localCourseKeys.pageNames(courseName, moduleName), + }); + const url = + "/api/courses/" + + encodeURIComponent(courseName) + + "/modules/" + + encodeURIComponent(moduleName) + + "/pages/" + + encodeURIComponent(pageName); + await axiosClient.delete(url); + + }, + onSuccess: (_, { moduleName, pageName }) => { + queryClient.invalidateQueries({ + queryKey: localCourseKeys.pageNames(courseName, moduleName), + }); + queryClient.invalidateQueries({ + queryKey: localCourseKeys.page(courseName, moduleName, pageName), + }); + }, + }); +}; diff --git a/nextjs/src/hooks/localCourse/quizHooks.ts b/nextjs/src/hooks/localCourse/quizHooks.ts index 2bfc68d..886a4a9 100644 --- a/nextjs/src/hooks/localCourse/quizHooks.ts +++ b/nextjs/src/hooks/localCourse/quizHooks.ts @@ -173,3 +173,37 @@ export const useCreateQuizMutation = () => { }, }); }; + +export const useDeleteQuizMutation = () => { + const { courseName } = useCourseContext(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + moduleName, + quizName, + }: { + moduleName: string; + quizName: string; + }) => { + const url = + "/api/courses/" + + encodeURIComponent(courseName) + + "/modules/" + + encodeURIComponent(moduleName) + + "/quizzes/" + + encodeURIComponent(quizName); + await axiosClient.delete(url); + queryClient.removeQueries({ + queryKey: localCourseKeys.quizNames(courseName, moduleName), + }); + }, + onSuccess: async (_, { moduleName, quizName }) => { + await queryClient.invalidateQueries({ + queryKey: localCourseKeys.quizNames(courseName, moduleName), + }); + await queryClient.invalidateQueries({ + queryKey: localCourseKeys.quiz(courseName, moduleName, quizName), + }); + }, + }); +};