mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 15:18:32 -06:00
fixing updating between server and client
This commit is contained in:
@@ -15,6 +15,7 @@ import LectureButtons from "./LectureButtons";
|
||||
import { useCourseContext } from "../../context/courseContext";
|
||||
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
|
||||
import { Lecture } from "@/models/local/lecture";
|
||||
import { useAuthoritativeUpdates } from "../../utils/useAuthoritativeUpdates";
|
||||
|
||||
export default function EditLecture({ lectureDay }: { lectureDay: string }) {
|
||||
const { courseName } = useCourseContext();
|
||||
@@ -27,24 +28,15 @@ export default function EditLecture({ lectureDay }: { lectureDay: string }) {
|
||||
.flatMap(({ lectures }) => lectures.map((lecture) => lecture))
|
||||
.find((l) => l.date === lectureDay);
|
||||
|
||||
const startingText = getLectureTextOrDefault(lecture, lectureDay);
|
||||
|
||||
const [text, setText] = useState(startingText);
|
||||
const [updateMonacoKey, setUpdateMonacoKey] = useState(1);
|
||||
const { clientIsAuthoritative, text, textUpdate, monacoKey } =
|
||||
useAuthoritativeUpdates({
|
||||
serverUpdatedAt: serverDataUpdatedAt,
|
||||
startingText: getLectureTextOrDefault(lecture, lectureDay),
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const [clientDataUpdatedAt, setClientDataUpdatedAt] =
|
||||
useState(serverDataUpdatedAt);
|
||||
|
||||
const textUpdate = useCallback((t: string) => {
|
||||
setText(t);
|
||||
setClientDataUpdatedAt(Date.now());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 500;
|
||||
const clientIsAuthoritative = serverDataUpdatedAt <= clientDataUpdatedAt;
|
||||
console.log("client is authoritative", clientIsAuthoritative);
|
||||
|
||||
const handler = setTimeout(() => {
|
||||
try {
|
||||
@@ -58,8 +50,7 @@ export default function EditLecture({ lectureDay }: { lectureDay: string }) {
|
||||
console.log(
|
||||
"client not authoritative, updating client with server data"
|
||||
);
|
||||
textUpdate(lectureToString(lecture));
|
||||
setUpdateMonacoKey((k) => k + 1);
|
||||
textUpdate(lectureToString(lecture), true);
|
||||
} else {
|
||||
console.log(
|
||||
"client not authoritative, but no lecture on server, this is a bug"
|
||||
@@ -76,18 +67,22 @@ export default function EditLecture({ lectureDay }: { lectureDay: string }) {
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [courseName, lecture, settings, text, textUpdate, updateLecture]);
|
||||
}, [
|
||||
clientIsAuthoritative,
|
||||
courseName,
|
||||
lecture,
|
||||
settings,
|
||||
text,
|
||||
textUpdate,
|
||||
updateLecture,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<EditLectureTitle lectureDay={lectureDay} />
|
||||
<div className="sm:columns-2 min-h-0 flex-1">
|
||||
<div className="flex-1 h-full">
|
||||
<MonacoEditor
|
||||
key={updateMonacoKey}
|
||||
value={text}
|
||||
onChange={textUpdate}
|
||||
/>
|
||||
<MonacoEditor key={monacoKey} value={text} onChange={textUpdate} />
|
||||
</div>
|
||||
<div className="h-full sm:block none overflow-auto">
|
||||
<div className="text-red-300">{error && error}</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
LocalAssignment,
|
||||
localAssignmentMarkdown,
|
||||
} from "@/models/local/assignment/localAssignment";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import AssignmentPreview from "./AssignmentPreview";
|
||||
import { getModuleItemUrl } from "@/services/urlUtils";
|
||||
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||
@@ -19,6 +19,7 @@ import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSu
|
||||
import { LocalCourseSettings } from "@/models/local/localCourseSettings";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AssignmentButtons } from "./AssignmentButtons";
|
||||
import { useAuthoritativeUpdates } from "@/app/course/[courseName]/utils/useAuthoritativeUpdates";
|
||||
|
||||
export default function EditAssignment({
|
||||
moduleName,
|
||||
@@ -34,31 +35,22 @@ export default function EditAssignment({
|
||||
useAssignmentQuery(moduleName, assignmentName);
|
||||
const updateAssignment = useUpdateAssignmentMutation();
|
||||
|
||||
const [assignmentText, setAssignmentText] = useState(
|
||||
localAssignmentMarkdown.toMarkdown(assignment)
|
||||
);
|
||||
|
||||
const [updateMonacoKey, setUpdateMonacoKey] = useState(1);
|
||||
const [clientDataUpdatedAt, setClientDataUpdatedAt] =
|
||||
useState(serverDataUpdatedAt);
|
||||
|
||||
const textUpdate = useCallback((t: string) => {
|
||||
setAssignmentText(t);
|
||||
setClientDataUpdatedAt(Date.now());
|
||||
}, []);
|
||||
const { clientIsAuthoritative, text, textUpdate, monacoKey } =
|
||||
useAuthoritativeUpdates({
|
||||
serverUpdatedAt: serverDataUpdatedAt,
|
||||
startingText: localAssignmentMarkdown.toMarkdown(assignment),
|
||||
});
|
||||
|
||||
const [error, setError] = useState("");
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 500;
|
||||
const clientIsAuthoritative = serverDataUpdatedAt <= clientDataUpdatedAt;
|
||||
console.log("client is authoritative", clientIsAuthoritative);
|
||||
|
||||
const handler = setTimeout(() => {
|
||||
try {
|
||||
const updatedAssignment: LocalAssignment =
|
||||
localAssignmentMarkdown.parseMarkdown(assignmentText);
|
||||
localAssignmentMarkdown.parseMarkdown(text);
|
||||
if (
|
||||
localAssignmentMarkdown.toMarkdown(assignment) !==
|
||||
localAssignmentMarkdown.toMarkdown(updatedAssignment)
|
||||
@@ -89,8 +81,7 @@ export default function EditAssignment({
|
||||
console.log(
|
||||
"client not authoritative, updating client with server data"
|
||||
);
|
||||
textUpdate(localAssignmentMarkdown.toMarkdown(assignment));
|
||||
setUpdateMonacoKey((k) => k + 1);
|
||||
textUpdate(localAssignmentMarkdown.toMarkdown(assignment), true);
|
||||
}
|
||||
}
|
||||
setError("");
|
||||
@@ -105,10 +96,12 @@ export default function EditAssignment({
|
||||
}, [
|
||||
assignment,
|
||||
assignmentName,
|
||||
assignmentText,
|
||||
clientIsAuthoritative,
|
||||
courseName,
|
||||
moduleName,
|
||||
router,
|
||||
text,
|
||||
textUpdate,
|
||||
updateAssignment,
|
||||
]);
|
||||
|
||||
@@ -121,11 +114,7 @@ export default function EditAssignment({
|
||||
</pre>
|
||||
)}
|
||||
<div className="flex-1 h-full">
|
||||
<MonacoEditor
|
||||
key={updateMonacoKey}
|
||||
value={assignmentText}
|
||||
onChange={textUpdate}
|
||||
/>
|
||||
<MonacoEditor key={monacoKey} value={text} onChange={textUpdate} />
|
||||
</div>
|
||||
<div className="flex-1 h-full">
|
||||
<div className="text-red-300">{error && error}</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ import ClientOnly from "@/components/ClientOnly";
|
||||
import { getModuleItemUrl } from "@/services/urlUtils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||
import { useAuthoritativeUpdates } from "@/app/course/[courseName]/utils/useAuthoritativeUpdates";
|
||||
|
||||
export default function EditPage({
|
||||
moduleName,
|
||||
@@ -21,69 +22,61 @@ export default function EditPage({
|
||||
}: {
|
||||
pageName: string;
|
||||
moduleName: string;
|
||||
}) {
|
||||
const [_, { dataUpdatedAt }] = usePageQuery(moduleName, pageName);
|
||||
|
||||
return (
|
||||
<InnerEditPage
|
||||
key={dataUpdatedAt}
|
||||
pageName={pageName}
|
||||
moduleName={moduleName}
|
||||
></InnerEditPage>
|
||||
);
|
||||
}
|
||||
|
||||
function InnerEditPage({
|
||||
moduleName,
|
||||
pageName,
|
||||
}: {
|
||||
pageName: string;
|
||||
moduleName: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { courseName } = useCourseContext();
|
||||
const [page, pageQuery] = usePageQuery(moduleName, pageName);
|
||||
const updatePage = useUpdatePageMutation();
|
||||
const [pageText, setPageText] = useState(
|
||||
localPageMarkdownUtils.toMarkdown(page)
|
||||
const [page, { dataUpdatedAt }] = usePageQuery(
|
||||
moduleName,
|
||||
pageName
|
||||
);
|
||||
const updatePage = useUpdatePageMutation();
|
||||
|
||||
const { clientIsAuthoritative, text, textUpdate, monacoKey } =
|
||||
useAuthoritativeUpdates({
|
||||
serverUpdatedAt: dataUpdatedAt,
|
||||
startingText: localPageMarkdownUtils.toMarkdown(page),
|
||||
});
|
||||
|
||||
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(() => {
|
||||
try {
|
||||
const updatedPage = localPageMarkdownUtils.parseMarkdown(pageText);
|
||||
const updatedPage = localPageMarkdownUtils.parseMarkdown(text);
|
||||
if (
|
||||
localPageMarkdownUtils.toMarkdown(page) !==
|
||||
localPageMarkdownUtils.toMarkdown(updatedPage)
|
||||
) {
|
||||
console.log("updating page");
|
||||
updatePage
|
||||
.mutateAsync({
|
||||
page: updatedPage,
|
||||
moduleName,
|
||||
pageName: updatedPage.name,
|
||||
previousModuleName: moduleName,
|
||||
previousPageName: pageName,
|
||||
courseName,
|
||||
})
|
||||
.then(() => {
|
||||
if (updatedPage.name !== pageName)
|
||||
router.replace(
|
||||
getModuleItemUrl(
|
||||
courseName,
|
||||
moduleName,
|
||||
"page",
|
||||
updatedPage.name
|
||||
)
|
||||
);
|
||||
});
|
||||
if (clientIsAuthoritative) {
|
||||
console.log("updating page");
|
||||
updatePage
|
||||
.mutateAsync({
|
||||
page: updatedPage,
|
||||
moduleName,
|
||||
pageName: updatedPage.name,
|
||||
previousModuleName: moduleName,
|
||||
previousPageName: pageName,
|
||||
courseName,
|
||||
})
|
||||
.then(() => {
|
||||
if (updatedPage.name !== pageName)
|
||||
router.replace(
|
||||
getModuleItemUrl(
|
||||
courseName,
|
||||
moduleName,
|
||||
"page",
|
||||
updatedPage.name
|
||||
)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
"client not authoritative, updating client with server data"
|
||||
);
|
||||
textUpdate(localPageMarkdownUtils.toMarkdown(page), true);
|
||||
}
|
||||
}
|
||||
setError("");
|
||||
} catch (e: any) {
|
||||
@@ -94,13 +87,23 @@ function InnerEditPage({
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [courseName, moduleName, page, pageName, pageText, router, updatePage]);
|
||||
}, [
|
||||
clientIsAuthoritative,
|
||||
courseName,
|
||||
moduleName,
|
||||
page,
|
||||
pageName,
|
||||
router,
|
||||
text,
|
||||
textUpdate,
|
||||
updatePage,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="columns-2 min-h-0 flex-1">
|
||||
<div className="flex-1 h-full">
|
||||
<MonacoEditor value={pageText} onChange={setPageText} />
|
||||
<MonacoEditor key={monacoKey} value={text} onChange={textUpdate} />
|
||||
</div>
|
||||
<div className="h-full">
|
||||
<div className="text-red-300">{error && error}</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import { MonacoEditor } from "@/components/editor/MonacoEditor";
|
||||
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import QuizPreview from "./QuizPreview";
|
||||
import { QuizButtons } from "./QuizButton";
|
||||
import ClientOnly from "@/components/ClientOnly";
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
useQuizQuery,
|
||||
useUpdateQuizMutation,
|
||||
} from "@/hooks/localCourse/quizHooks";
|
||||
import { useAuthoritativeUpdates } from "../../../../utils/useAuthoritativeUpdates";
|
||||
|
||||
const helpString = `QUESTION REFERENCE
|
||||
---
|
||||
@@ -61,22 +62,6 @@ export default function EditQuiz({
|
||||
}: {
|
||||
quizName: string;
|
||||
moduleName: string;
|
||||
}) {
|
||||
const [_, { dataUpdatedAt }] = useQuizQuery(moduleName, quizName);
|
||||
return (
|
||||
<InnerEditQuiz
|
||||
key={dataUpdatedAt}
|
||||
quizName={quizName}
|
||||
moduleName={moduleName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export function InnerEditQuiz({
|
||||
moduleName,
|
||||
quizName,
|
||||
}: {
|
||||
quizName: string;
|
||||
moduleName: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { courseName } = useCourseContext();
|
||||
@@ -85,34 +70,25 @@ export function InnerEditQuiz({
|
||||
quizName
|
||||
);
|
||||
const updateQuizMutation = useUpdateQuizMutation();
|
||||
const [quizText, setQuizText] = useState(quizMarkdownUtils.toMarkdown(quiz));
|
||||
const { clientIsAuthoritative, text, textUpdate, monacoKey } =
|
||||
useAuthoritativeUpdates({
|
||||
serverUpdatedAt: serverDataUpdatedAt,
|
||||
startingText: quizMarkdownUtils.toMarkdown(quiz),
|
||||
});
|
||||
|
||||
const [updateMonacoKey, setUpdateMonacoKey] = useState(1);
|
||||
const [clientDataUpdatedAt, setClientDataUpdatedAt] =
|
||||
useState(serverDataUpdatedAt);
|
||||
const [error, setError] = useState("");
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
|
||||
const textUpdate = useCallback((t: string) => {
|
||||
setQuizText(t);
|
||||
setClientDataUpdatedAt(Date.now());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 1000;
|
||||
const clientIsAuthoritative = serverDataUpdatedAt <= clientDataUpdatedAt;
|
||||
console.log("client is authoritative", clientIsAuthoritative);
|
||||
|
||||
const handler = setTimeout(async () => {
|
||||
try {
|
||||
if (
|
||||
quizMarkdownUtils.toMarkdown(quiz) !==
|
||||
quizMarkdownUtils.toMarkdown(
|
||||
quizMarkdownUtils.parseMarkdown(quizText)
|
||||
)
|
||||
quizMarkdownUtils.toMarkdown(quizMarkdownUtils.parseMarkdown(text))
|
||||
) {
|
||||
if (clientIsAuthoritative) {
|
||||
const updatedQuiz = quizMarkdownUtils.parseMarkdown(quizText);
|
||||
const updatedQuiz = quizMarkdownUtils.parseMarkdown(text);
|
||||
await updateQuizMutation
|
||||
.mutateAsync({
|
||||
quiz: updatedQuiz,
|
||||
@@ -137,8 +113,7 @@ export function InnerEditQuiz({
|
||||
console.log(
|
||||
"client not authoritative, updating client with server data"
|
||||
);
|
||||
textUpdate(quizMarkdownUtils.toMarkdown(quiz));
|
||||
setUpdateMonacoKey((k) => k + 1);
|
||||
textUpdate(quizMarkdownUtils.toMarkdown(quiz), true);
|
||||
}
|
||||
}
|
||||
setError("");
|
||||
@@ -151,19 +126,17 @@ export function InnerEditQuiz({
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [
|
||||
clientDataUpdatedAt,
|
||||
clientIsAuthoritative,
|
||||
courseName,
|
||||
moduleName,
|
||||
quiz,
|
||||
quizName,
|
||||
quizText,
|
||||
router,
|
||||
serverDataUpdatedAt,
|
||||
text,
|
||||
textUpdate,
|
||||
updateQuizMutation,
|
||||
]);
|
||||
|
||||
console.log("updateMonacoKey", updateMonacoKey);
|
||||
return (
|
||||
<div className="h-full flex flex-col align-middle px-1">
|
||||
<div className={"min-h-96 h-full flex flex-row w-full"}>
|
||||
@@ -173,11 +146,7 @@ export function InnerEditQuiz({
|
||||
</pre>
|
||||
)}
|
||||
<div className="flex-1 h-full">
|
||||
<MonacoEditor
|
||||
key={updateMonacoKey}
|
||||
value={quizText}
|
||||
onChange={textUpdate}
|
||||
/>
|
||||
<MonacoEditor key={monacoKey} value={text} onChange={textUpdate} />
|
||||
</div>
|
||||
<div className="flex-1 h-full">
|
||||
<div className="text-red-300">{error && error}</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
import { useState, useMemo, useCallback } from "react";
|
||||
|
||||
export function useAuthoritativeUpdates({
|
||||
serverUpdatedAt,
|
||||
startingText,
|
||||
}: {
|
||||
serverUpdatedAt: number;
|
||||
startingText: string;
|
||||
}) {
|
||||
const [text, setText] = useState(startingText);
|
||||
const [clientDataUpdatedAt, setClientDataUpdatedAt] =
|
||||
useState(serverUpdatedAt);
|
||||
const [updateMonacoKey, setUpdateMonacoKey] = useState(1);
|
||||
|
||||
const clientIsAuthoritative = useMemo(
|
||||
() => serverUpdatedAt <= clientDataUpdatedAt,
|
||||
[clientDataUpdatedAt, serverUpdatedAt]
|
||||
);
|
||||
|
||||
console.log("client is authoritative", clientIsAuthoritative);
|
||||
const textUpdate = useCallback((t: string, updateMonaco: boolean = false) => {
|
||||
setText(t);
|
||||
setClientDataUpdatedAt(Date.now());
|
||||
if (updateMonaco) setUpdateMonacoKey((t) => t + 1);
|
||||
}, []);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
clientIsAuthoritative,
|
||||
textUpdate,
|
||||
text,
|
||||
monacoKey: updateMonacoKey,
|
||||
}),
|
||||
[clientIsAuthoritative, text, textUpdate, updateMonacoKey]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user