moving v2 to top level

This commit is contained in:
2024-12-17 09:19:21 -07:00
parent 5f0b3554dc
commit 576ee02afb
468 changed files with 79 additions and 15430 deletions

View File

@@ -0,0 +1,112 @@
"use client";
import { MonacoEditor } from "@/components/editor/MonacoEditor";
import {
useLecturesSuspenseQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import {
lectureToString,
parseLecture,
} from "@/services/fileStorage/utils/lectureUtils";
import { useEffect, useState } from "react";
import LecturePreview from "./LecturePreview";
import EditLectureTitle from "./EditLectureTitle";
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();
const [settings] = useLocalCourseSettingsQuery();
const [weeks, { dataUpdatedAt: serverDataUpdatedAt, isFetching }] =
useLecturesSuspenseQuery();
const updateLecture = useLectureUpdateMutation();
const lecture = weeks
.flatMap(({ lectures }) => lectures.map((lecture) => lecture))
.find((l) => l.date === lectureDay);
const { clientIsAuthoritative, text, textUpdate, monacoKey } =
useAuthoritativeUpdates({
serverUpdatedAt: serverDataUpdatedAt,
startingText: getLectureTextOrDefault(lecture, lectureDay),
});
const [error, setError] = useState("");
useEffect(() => {
const delay = 500;
const handler = setTimeout(() => {
try {
if (isFetching || updateLecture.isPending) {
console.log("network requests in progress, not updating page");
return;
}
const parsed = parseLecture(text);
if (!lecture || lectureToString(parsed) !== lectureToString(lecture)) {
if (clientIsAuthoritative) {
console.log("updating lecture");
updateLecture.mutate({ lecture: parsed, settings, courseName });
} else {
if (lecture) {
console.log(
"client not authoritative, updating client with server lecture"
);
textUpdate(lectureToString(lecture), true);
} else {
console.log(
"client not authoritative, but no lecture on server, this is a bug"
);
}
}
}
setError("");
} catch (e: any) {
setError(e.toString());
}
}, delay);
return () => {
clearTimeout(handler);
};
}, [
clientIsAuthoritative,
courseName,
isFetching,
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={monacoKey} value={text} onChange={textUpdate} />
</div>
<div className="h-full sm:block none overflow-auto">
<div className="text-red-300">{error && error}</div>
{lecture && <LecturePreview lecture={lecture} />}
</div>
</div>
<LectureButtons lectureDay={lectureDay} />
</div>
);
}
function getLectureTextOrDefault(
lecture: Lecture | undefined,
lectureDay: string
) {
return lecture
? lectureToString(lecture)
: `Name:
Date: ${lectureDay}
---
`;
}

View File

@@ -0,0 +1,42 @@
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { getDayOfWeek } from "@/models/local/localCourseSettings";
import { getDateFromString } from "@/models/local/utils/timeUtils";
import { getLectureWeekName } from "@/services/fileStorage/utils/lectureUtils";
import { getCourseUrl, getLecturePreviewUrl } from "@/services/urlUtils";
import { useCourseContext } from "../../context/courseContext";
import Link from "next/link";
export default function EditLectureTitle({
lectureDay,
}: {
lectureDay: string;
}) {
const [settings] = useLocalCourseSettingsQuery();
const { courseName } = useCourseContext();
const lectureDate = getDateFromString(lectureDay);
const lectureWeekName = getLectureWeekName(settings.startDate, lectureDay);
return (
<div className="flex justify-between sm:flex-row flex-col">
<div className="my-auto">
<Link className="btn hidden sm:inline" href={getCourseUrl(courseName)}>
{courseName}
</Link>
</div>
<div className="flex justify-center ">
<h3 className="mt-auto me-3 text-slate-500 ">Lecture</h3>
<h1 className="">
{lectureDate && getDayOfWeek(lectureDate)}{" "}
{lectureWeekName.toUpperCase()}
</h1>
</div>
<div className="text-end my-auto flex">
<Link
className="btn inline text-center flex-grow m-1"
href={getLecturePreviewUrl(courseName, lectureDay)}
>
preview
</Link>
</div>
</div>
);
}

View File

@@ -0,0 +1,67 @@
"use client";
import Modal, { useModal } from "@/components/Modal";
import { Spinner } from "@/components/Spinner";
import { getCourseUrl } from "@/services/urlUtils";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useCourseContext } from "../../context/courseContext";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { useDeleteLectureMutation } from "@/hooks/localCourse/lectureHooks";
import Link from "next/link";
export default function LectureButtons({ lectureDay }: { lectureDay: string }) {
const { courseName } = useCourseContext();
const [settings] = useLocalCourseSettingsQuery();
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const modal = useModal();
const deleteLecture = useDeleteLectureMutation();
return (
<div className="p-5 flex flex-row justify-end gap-3">
<div>
<Modal
modalControl={modal}
buttonText="Delete Lecture"
buttonClass="btn-danger"
modalWidth="w-1/5"
>
{({ closeModal }) => (
<div>
<div className="text-center">
Are you sure you want to delete this lecture?
</div>
<br />
<div className="flex justify-around gap-3">
<button
onClick={async () => {
setIsLoading(true);
router.push(getCourseUrl(courseName));
await deleteLecture.mutateAsync({
courseName,
settings,
lectureDay,
});
}}
disabled={isLoading}
className="btn-danger"
>
Yes
</button>
<button onClick={closeModal} disabled={isLoading}>
No
</button>
</div>
{isLoading && <Spinner />}
</div>
)}
</Modal>
</div>
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Go Back
</Link>
{isLoading && <Spinner />}
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { Lecture } from "@/models/local/lecture";
import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils";
export default function LecturePreview({ lecture }: { lecture: Lecture }) {
const [settings] = useLocalCourseSettingsQuery();
return (
<>
<section className="border-b-slate-700 border-b-4">
<div className="text-center font-extrabold">{lecture.name}</div>
<div className="text-center font-bold text-slate-400">{lecture.date}</div>
</section>
<section>
<div
className="markdownPreview text-xl"
dangerouslySetInnerHTML={{
__html: markdownToHTMLSafe(lecture.content, settings),
}}
></div>
</section>
</>
);
}

View File

@@ -0,0 +1,24 @@
import { Suspense } from "react";
import CourseContextProvider from "../../context/CourseContextProvider";
export default async function LectureLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ courseName: string; lectureDay: string }>;
}) {
const { courseName, lectureDay } = await params;
const decodedCourseName = decodeURIComponent(courseName);
if (courseName.includes(".js.map")) {
console.log("cannot load course that is .js.map " + decodedCourseName);
return <div></div>;
}
return (
<Suspense>
<CourseContextProvider localCourseName={decodedCourseName}>
{children}
</CourseContextProvider>
</Suspense>
);
}

View File

@@ -0,0 +1,22 @@
import EditLecture from "./EditLecture";
import {
getDateFromStringOrThrow,
getDateOnlyMarkdownString,
} from "@/models/local/utils/timeUtils";
export const dynamic = "force-dynamic";
export default async function page({
params,
}: {
params: Promise<{ lectureDay: string }>;
}) {
const { lectureDay } = await params;
const decodedLectureDay = decodeURIComponent(lectureDay);
console.log(decodedLectureDay);
const lectureDate = getDateFromStringOrThrow(
decodedLectureDay,
"lecture day in lecture page"
);
const lectureDayOnly = getDateOnlyMarkdownString(lectureDate);
return <EditLecture lectureDay={lectureDayOnly} />;
}

View File

@@ -0,0 +1,55 @@
"use client";
import LecturePreview from "../LecturePreview";
import { getCourseUrl, getLectureUrl } from "@/services/urlUtils";
import { useCourseContext } from "../../../context/courseContext";
import Link from "next/link";
import { useLecturesSuspenseQuery } from "@/hooks/localCourse/lectureHooks";
export default function LecturePreviewPage({
lectureDay,
}: {
lectureDay: string;
}) {
const { courseName } = useCourseContext();
const [weeks] = useLecturesSuspenseQuery();
const lecture = weeks
.flatMap(({ lectures }) => lectures.map((lecture) => lecture))
.find((l) => l.date === lectureDay);
if (!lecture) {
return <div>lecture not found for day</div>;
}
return (
<div className="flex h-full xl:flex-row flex-col ">
<div className="flex-shrink flex-1 pb-1 ms-3 xl:ms-0 flex flex-row flex-wrap gap-3 content-start ">
<div className="">
<Link
className="btn"
href={getLectureUrl(courseName, lectureDay)}
shallow={true}
>
Edit Page
</Link>
</div>
<div className="">
<Link className="btn" href={getCourseUrl(courseName)} shallow={true}>
Course Calendar
</Link>
</div>
</div>
<div className="flex justify-center min-h-0 px-2">
<div
className="
w-full max-w-screen-lg
border-slate-700 border-4 rounded-md
p-3 overflow-auto
"
>
<LecturePreview lecture={lecture} />
</div>
</div>
<div className="flex-shrink flex-1"></div>
</div>
);
}

View File

@@ -0,0 +1,27 @@
import {
getDateFromStringOrThrow,
getDateOnlyMarkdownString,
} from "@/models/local/utils/timeUtils";
import LecturePreviewPage from "./LecturePreviewPage";
export const dynamic = "force-dynamic";
export default async function Page({
params,
}: {
params: Promise<{ lectureDay: string }>;
}) {
const { lectureDay } = await params;
const decodedLectureDay = decodeURIComponent(lectureDay);
const lectureDate = getDateFromStringOrThrow(
decodedLectureDay,
"lecture day in lecture page"
);
const lectureDayOnly = getDateOnlyMarkdownString(lectureDate);
console.log(lectureDayOnly);
return (
<>
<LecturePreviewPage lectureDay={lectureDayOnly} />
</>
);
}