mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-27 07:58:31 -06:00
moving v2 to top level
This commit is contained in:
112
src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx
Normal file
112
src/app/course/[courseName]/lecture/[lectureDay]/EditLecture.tsx
Normal 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}
|
||||
---
|
||||
`;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
24
src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx
Normal file
24
src/app/course/[courseName]/lecture/[lectureDay]/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
22
src/app/course/[courseName]/lecture/[lectureDay]/page.tsx
Normal file
22
src/app/course/[courseName]/lecture/[lectureDay]/page.tsx
Normal 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} />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user