From 5c1628080778288b16e5a5af7d67d7b08b21f7d1 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Fri, 15 Nov 2024 08:22:33 -0700 Subject: [PATCH] server websockets --- nextjs/build.sh | 2 +- .../lecture/[lectureDay]/EditLecture.tsx | 12 +- .../[assignmentName]/EditAssignment.tsx | 16 +++ .../[moduleName]/page/[pageName]/EditPage.tsx | 24 +++- .../[moduleName]/quiz/[quizName]/EditQuiz.tsx | 16 +++ .../app/realtime/ClientCacheInvalidation.tsx | 105 +++++++++++++++--- nextjs/src/websocket.js | 13 +-- 7 files changed, 161 insertions(+), 27 deletions(-) diff --git a/nextjs/build.sh b/nextjs/build.sh index d6a3ca2..d271be9 100755 --- a/nextjs/build.sh +++ b/nextjs/build.sh @@ -1,7 +1,7 @@ #!/bin/bash MAJOR_VERSION="2" -MINOR_VERSION="2" +MINOR_VERSION="3" VERSION="$MAJOR_VERSION.$MINOR_VERSION" docker build -t canvas_management:$VERSION . diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx index 8c2fdfd..4e60196 100644 --- a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx +++ b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx @@ -1,7 +1,10 @@ "use client"; import { MonacoEditor } from "@/components/editor/MonacoEditor"; -import { useLecturesSuspenseQuery, useLectureUpdateMutation } from "@/hooks/localCourse/lectureHooks"; +import { + useLecturesSuspenseQuery, + useLectureUpdateMutation, +} from "@/hooks/localCourse/lectureHooks"; import { lectureToString, parseLecture, @@ -10,11 +13,16 @@ import { useEffect, useState } from "react"; import LecturePreview from "./LecturePreview"; import EditLectureTitle from "./EditLectureTitle"; import LectureButtons from "./LectureButtons"; -import { trpc } from "@/services/trpc/utils"; import { useCourseContext } from "../../context/courseContext"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; export default function EditLecture({ lectureDay }: { lectureDay: string }) { + const [_, {dataUpdatedAt}] = useLecturesSuspenseQuery(); + return ( + + ); +} +export function InnerEditLecture({ lectureDay }: { lectureDay: string }) { const { courseName } = useCourseContext(); const [settings] = useLocalCourseSettingsQuery(); const [weeks] = useLecturesSuspenseQuery(); diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx index 5028508..b5a97dd 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/EditAssignment.tsx @@ -26,6 +26,22 @@ export default function EditAssignment({ }: { assignmentName: string; moduleName: string; +}) { + const [_, {dataUpdatedAt}] = useAssignmentQuery(moduleName, assignmentName); + return ( + + ); +} +export function InnerEditAssignment({ + moduleName, + assignmentName, +}: { + assignmentName: string; + moduleName: string; }) { const router = useRouter(); const { courseName } = useCourseContext(); diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx index d94b20b..46fa15b 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/EditPage.tsx @@ -21,10 +21,28 @@ export default function EditPage({ }: { pageName: string; moduleName: string; +}) { + const [_, { dataUpdatedAt }] = usePageQuery(moduleName, pageName); + + return ( + + ); +} + +function InnerEditPage({ + moduleName, + pageName, +}: { + pageName: string; + moduleName: string; }) { const router = useRouter(); const { courseName } = useCourseContext(); - const [page] = usePageQuery(moduleName, pageName); + const [page, pageQuery] = usePageQuery(moduleName, pageName); const updatePage = useUpdatePageMutation(); const [pageText, setPageText] = useState( localPageMarkdownUtils.toMarkdown(page) @@ -32,6 +50,10 @@ export default function EditPage({ const [error, setError] = useState(""); const [settings] = useLocalCourseSettingsQuery(); + useEffect(() => { + console.log("page data updated on sever", pageQuery.dataUpdatedAt); + }, [pageQuery.dataUpdatedAt]); + useEffect(() => { const delay = 500; const handler = setTimeout(() => { 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 941abe2..22722a6 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 @@ -61,6 +61,22 @@ export default function EditQuiz({ }: { quizName: string; moduleName: string; +}) { + const [_, {dataUpdatedAt}] = useQuizQuery(moduleName, quizName); + return ( + + ); +} +export function InnerEditQuiz({ + moduleName, + quizName, +}: { + quizName: string; + moduleName: string; }) { const router = useRouter(); const { courseName } = useCourseContext(); diff --git a/nextjs/src/app/realtime/ClientCacheInvalidation.tsx b/nextjs/src/app/realtime/ClientCacheInvalidation.tsx index accfa53..2eb961b 100644 --- a/nextjs/src/app/realtime/ClientCacheInvalidation.tsx +++ b/nextjs/src/app/realtime/ClientCacheInvalidation.tsx @@ -1,7 +1,8 @@ "use client"; -import React, { useEffect } from 'react'; -import { io, Socket } from 'socket.io-client'; +import { trpc } from "@/services/trpc/utils"; +import React, { useEffect } from "react"; +import { io, Socket } from "socket.io-client"; interface ServerToClientEvents { message: (data: string) => void; @@ -12,28 +13,106 @@ interface ClientToServerEvents { sendMessage: (data: string) => void; } -const socket: Socket = io('/'); +const socket: Socket = io("/"); + +function removeFileExtension(fileName: string): string { + const lastDotIndex = fileName.lastIndexOf("."); + if (lastDotIndex > 0) { + return fileName.substring(0, lastDotIndex); + } + return fileName; +} export function ClientCacheInvalidation() { + const utils = trpc.useUtils(); useEffect(() => { - socket.on('message', (data) => { - console.log('Received message:', data); + socket.on("message", (data) => { + console.log("Received message:", data); }); - socket.on('fileChanged', (filePath) => { - console.log('File changed:', filePath); + socket.on("fileChanged", (filePath) => { + const [courseName, moduleOrLectures, itemType, itemFile] = + filePath.split("/"); + + const itemName = removeFileExtension(itemFile); + + const allParts = [courseName, moduleOrLectures, itemType, itemName]; + + if (moduleOrLectures === "settings.yml") { + utils.settings.allCoursesSettings.invalidate(); + utils.settings.courseSettings.invalidate({ courseName }); + return; + } + + if (moduleOrLectures === "00 - lectures") { + console.log("lecture updated on FS ", allParts); + utils.lectures.getLectures.invalidate({ courseName }); + return; + } + + if (itemType === "assignments") { + console.log("assignment updated on FS ", allParts); + utils.assignment.getAllAssignments.invalidate({ + courseName, + moduleName: moduleOrLectures, + }); + utils.assignment.getAssignment.invalidate({ + courseName, + moduleName: moduleOrLectures, + assignmentName: itemName, + }); + return; + } + + if (itemType === "quizzes") { + console.log("quiz updated on FS ", allParts); + utils.quiz.getAllQuizzes.invalidate({ + courseName, + moduleName: moduleOrLectures, + }); + utils.quiz.getQuiz.invalidate({ + courseName, + moduleName: moduleOrLectures, + quizName: itemName, + }); + return; + } + + if (itemType === "pages") { + console.log("page updated on FS ", allParts); + utils.page.getAllPages.invalidate({ + courseName, + moduleName: moduleOrLectures, + }); + utils.page.getPage.invalidate({ + courseName, + moduleName: moduleOrLectures, + pageName: itemName, + }); + return; + } }); - socket.on('connect_error', (error) => { - console.error('Connection error:', error); + socket.on("connect_error", (error) => { + console.error("Connection error:", error); }); return () => { - socket.off('message'); - socket.off('fileChanged'); - socket.off('connect_error'); + socket.off("message"); + socket.off("fileChanged"); + socket.off("connect_error"); }; - }, []); + }, [ + utils.assignment.getAllAssignments, + utils.assignment.getAssignment, + utils.lectures.getLectures, + utils.page.getAllPages, + utils.page.getPage, + utils.quiz.getAllQuizzes, + utils.quiz.getQuiz, + utils.settings.allCoursesSettings, + utils.settings.courseSettings, + ]); return <>; } diff --git a/nextjs/src/websocket.js b/nextjs/src/websocket.js index 179efde..d214c4d 100644 --- a/nextjs/src/websocket.js +++ b/nextjs/src/websocket.js @@ -12,7 +12,7 @@ const port = 3000; const app = next({ dev, hostname, port }); const handler = app.getRequestHandler(); -const folderToWatch = path.join(process.cwd(), "./storage"); +const folderToWatch = path.join(process.cwd(), "./storage", "/"); console.log("watching folder", folderToWatch); app.prepare().then(() => { @@ -23,21 +23,14 @@ app.prepare().then(() => { io.on("connection", (socket) => { console.log("websocket connection created"); - - // Define a change event handler const changeHandler = (filePath) => { - const relativePath = filePath.replace(folderToWatch, "") + const relativePath = filePath.replace(folderToWatch, ""); console.log(`Sending file changed websocket message: ${relativePath}`); - - - socket.emit("fileChanged", `File changed: ${filePath}`); + socket.emit("fileChanged", relativePath); }; - - // Attach the change event handler watcher.on("change", changeHandler); - // Clean up the change event handler when the client disconnects socket.on("disconnect", () => { console.log("Client disconnected"); watcher.off("change", changeHandler); // Remove the event listener