Skip to content
Snippets Groups Projects
Commit bf1d3ee7 authored by Dorian Koch's avatar Dorian Koch
Browse files

ts: Remove react-dom-router from dynamic player and embed

parent f6dedddc
No related branches found
No related tags found
No related merge requests found
......@@ -13,7 +13,7 @@ export class FallbackErrorBoundary extends React.Component<any, { error?: any }>
}
componentDidCatch(error: any, info: ErrorInfo) {
console.error(error, info);
console.error("The following error was caught by FallbackErrorBoundary:", error, info);
}
clearError() {
......
import { MouseEvent, useEffect, useRef, useState } from "react";
import "video.js/dist/video-js.min.css";
import "@silvermine/videojs-quality-selector/dist/css/quality-selector.css";
import { Backend } from "@/api/Backend";
import { useLoaderData, useNavigate, useParams } from "react-router-dom";
import Link from "next/link";
import { useBackendContext } from "./BackendProvider";
import { timestampToString, stringToStyledHtml } from "@/misc/Formatting";
......@@ -19,7 +17,6 @@ import { showError } from "@/misc/ErrorHandlers";
import type React from "react";
import {
AuthenticationStatusResponse,
GetCourseResponse,
authentication_method,
authentication_methods,
......@@ -28,33 +25,8 @@ import {
lecture,
} from "@/api/api_v1_types";
import Dropdown from "react-bootstrap/Dropdown";
export function getLectureDataLoader(api: Backend, hasUserInfo: boolean) {
const actualLoader = async ({
params,
}: {
params: { course_id_string?: string; lecture_id?: string };
}) => {
let course: GetCourseResponse = await api.getCourse(params.course_id_string!, true);
let lecture = course.lectures!.find((l) => l.id === parseInt(params.lecture_id!));
if (lecture === undefined) {
throw new Error("Lecture not found");
}
let perms: AuthenticationStatusResponse = {
authenticated_methods: ["public"],
is_lecture_authenticated: true,
};
if (
lecture.authentication_methods.includes("public") === false &&
lecture.authentication_methods.length > 0
) {
perms = await api.getAuthenticationStatus({ lecture_id: lecture.id });
}
return { course, perms, loaderHadUserInfo: hasUserInfo };
};
return actualLoader;
}
import { ResourceType } from "@/misc/PromiseHelpers";
import { PlayerData } from "@/pages/dynamic_player";
function VideoPlayer({ lecture, className }: { lecture: lecture; className?: string }) {
const videoRef = useRef<HTMLVideoElement>(null);
......@@ -185,10 +157,17 @@ function VideoPlayer({ lecture, className }: { lecture: lecture; className?: str
);
}
function KapitelPopover({ timeStr, closePop }: { timeStr: string; closePop: () => void }) {
function KapitelPopover({
timeStr,
closePop,
lectureId,
}: {
timeStr: string;
closePop: () => void;
lectureId: number;
}) {
const userContext = useUserContext();
const api = useBackendContext();
const lecture_id = parseInt(useParams().lecture_id!);
const reloadFunc = useReloadBoundary();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
......@@ -207,7 +186,7 @@ function KapitelPopover({ timeStr, closePop }: { timeStr: string; closePop: () =
if (userContext.canEditStuff()) {
api.createOMObject("chapter", {
parent_type: "lecture",
parent_id: lecture_id,
parent_id: lectureId,
values: {
start_time: time,
name: text,
......@@ -222,7 +201,7 @@ function KapitelPopover({ timeStr, closePop }: { timeStr: string; closePop: () =
showError(e, "Unable to create chapter");
});
} else {
api.suggestChapter(lecture_id, {
api.suggestChapter(lectureId, {
start_time: time,
name: text,
})
......@@ -277,7 +256,7 @@ function AuthorizeHelper({
authed_methods: authentication_methods;
}) {
const api = useBackendContext();
const navigate = useNavigate();
const reloadFunc = useReloadBoundary();
const [userPwErrorState, setUserPwErrorState] = useState();
const userContext = useUserContext();
const hasUserInfo = userContext.hasUserInfo();
......@@ -285,9 +264,10 @@ function AuthorizeHelper({
useEffect(() => {
if (hasUserInfo === true) {
// reload page when the user logs in
navigate(".", { replace: true });
reloadFunc();
}
}, [navigate, hasUserInfo]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasUserInfo]);
const startOauth = (method: authentication_method) => {
if (method !== "moodle" && method !== "rwth") return;
......@@ -299,7 +279,7 @@ function AuthorizeHelper({
let popInterval = setInterval(() => {
if (pop.closed) {
clearInterval(popInterval);
navigate(".", { replace: true });
reloadFunc();
}
}, 100);
}
......@@ -315,9 +295,7 @@ function AuthorizeHelper({
let user = form.username.value;
let pass = form.password.value;
api.authenticatePassword({ username: user, password: pass, lecture_id: lecture.id })
.then((res) => {
navigate(".", { replace: true });
})
.then(reloadFunc)
.catch((e) => {
setUserPwErrorState(e);
});
......@@ -573,14 +551,10 @@ export function DownloadSources({
);
}
export function EmbeddedPlayer() {
const { course, perms } = useLoaderData() as {
course: GetCourseResponse;
perms: AuthenticationStatusResponse;
};
export function EmbeddedPlayer({ playerData }: { playerData: ResourceType<PlayerData> }) {
const { course, perms, lectureId } = playerData.read()!;
let lecture_id = parseInt(useParams().lecture_id!);
const lecture = course.lectures!.find((l) => l.id === lecture_id)!;
const lecture = course.lectures!.find((l) => l.id === lectureId)!;
useEffect(() => {
import("video.js");
......@@ -608,17 +582,12 @@ export function EmbeddedPlayer() {
return <>{pageContent} </>;
}
export default function Player() {
export default function Player({ playerData }: { playerData: ResourceType<PlayerData> }) {
const { course, lectureId, perms, loaderHadUserInfo } = playerData.read()!;
const userContext = useUserContext();
const hasUserInfo = userContext.hasUserInfo();
const { course, perms, loaderHadUserInfo } = useLoaderData() as {
course: GetCourseResponse;
perms: AuthenticationStatusResponse;
loaderHadUserInfo: boolean;
};
let lecture_id = parseInt(useParams().lecture_id!);
const lecture = course.lectures!.find((l) => l.id === lecture_id)!;
const lecture = course.lectures!.find((l) => l.id === lectureId)!;
useEffect(() => {
import("video.js");
......@@ -645,7 +614,12 @@ export default function Player() {
let tstr = timestampToString(timestamp);
setPopContent(
<KapitelPopover timeStr={tstr} key={tstr} closePop={() => setShowPop(false)} />,
<KapitelPopover
timeStr={tstr}
key={tstr}
closePop={() => setShowPop(false)}
lectureId={lectureId}
/>,
);
setShowPop(true);
});
......
......@@ -117,7 +117,7 @@ export function ErrorPage({
}
return (
<div className="alert alert-danger" role="alert">
<h4 className="alert-heading">Ein unerwarteter Fehler hat dich besucht</h4>
<h4 className="alert-heading">Unerwarteter Fehler!</h4>
<p>{mainMessage}</p>
<div>
Falls das Problem länger bestehen sollte, schreib uns bitte eine Mail an{" "}
......@@ -131,12 +131,13 @@ export function ErrorPage({
: "Seite auf welcher der Fehler aufgetreten ist"}
</li>
<li>Fehler: {error.message}</li>
<li>evt. was du getan hast als der Fehler aufgetreten ist</li>
<li>Was hast du getan bevor der Fehler aufgetreten ist?</li>
<li>Wie kann man den Fehler reproduzieren?</li>
</ul>
Wir werden uns dann schnellst möglich darum kümmern.
</div>
<button type="button" className="btn btn-success mb-2" onClick={reloadFunc}>
<span className="bi bi-arrow-counterclockwise" />
<button type="button" className="btn btn-success mt-2" onClick={reloadFunc}>
<span className="bi bi-arrow-counterclockwise me-1" />
Neuladen
</button>
</div>
......
import { useEffect, useState } from "react";
import { RouterProvider, createBrowserRouter, useRouteError } from "react-router-dom";
import nextConfig from "@/../basepath";
import Four04 from "./404";
import { EmbeddedPlayer, getLectureDataLoader } from "@/components/Player";
import { useBackendContext } from "@/components/BackendProvider";
import { ReloadBoundary } from "@/components/ReloadBoundary";
import { ErrorPage } from "@/misc/ErrorHandlers";
import { useUserContext } from "@/components/UserDataProvider";
function Custom404() {
const error = useRouteError();
return (
<ErrorPage
error={error}
objectName="Vorlesung"
expectedErrorCodes={[
"unauthorized",
"access_forbidden",
"unknown_object",
"rate_limited",
]}
/>
);
}
function ActualRedirector() {
const api = useBackendContext();
const userContext = useUserContext();
const router = createBrowserRouter(
[
{
path: ":course_id_string/:lecture_id/embed",
loader: getLectureDataLoader(api, userContext.hasUserInfo()),
errorElement: <Custom404 />,
element: <EmbeddedPlayer />,
},
{
path: "404",
element: <Four04 />,
},
{
path: "*",
element: <>* matched dynamic_embed</>,
},
],
{ basename: nextConfig.basePath },
);
const reload = () => {
router.navigate(".", { replace: true });
};
return (
<ReloadBoundary reloadFunc={reload}>
<RouterProvider router={router} />
</ReloadBoundary>
);
}
import { ActualRedirector } from "./dynamic_player";
// This only exists, so that we can effectively differentiate between player and embed in DefaultLayout.tsx (to remove the page header/footer for embeds)
// Necessary so that we can render the correct layout while building the page (since we can't use the actual pathname at build time)
export default function DynamicEmbed() {
// The following is a hack to force nextjs to not pre-render this page at build time (which obv wouldnt make sense)
const [clientsideForcer, setClientsideForcer] = useState(false);
......
import { useEffect, useState } from "react";
import { RouterProvider, createBrowserRouter, useRouteError } from "react-router-dom";
import nextConfig from "@/../basepath";
import { Suspense, useEffect, useState } from "react";
import Four04 from "./404";
import Player, { getLectureDataLoader } from "@/components/Player";
import { useBackendContext } from "@/components/BackendProvider";
import { ReloadBoundary } from "@/components/ReloadBoundary";
import { ErrorPage } from "@/misc/ErrorHandlers";
import { useUserContext } from "@/components/UserDataProvider";
import { usePathname } from "next/navigation";
import { Backend } from "@/api/Backend";
import { AuthenticationStatusResponse, GetCourseResponse } from "@/api/api_v1_types";
import { ResourceType, fetchDataWrapper } from "@/misc/PromiseHelpers";
import Player, { EmbeddedPlayer } from "@/components/Player";
import { ErrorPage } from "@/misc/ErrorHandlers";
import { FallbackErrorBoundary } from "@/components/FallbackErrorBoundary";
export interface PlayerData {
course: GetCourseResponse;
perms: AuthenticationStatusResponse;
loaderHadUserInfo: boolean;
lectureId: number;
}
async function dataLoader(
api: Backend,
course_id_string: string,
lecture_id: string,
hasUserInfo: boolean,
): Promise<PlayerData> {
let course: GetCourseResponse = await api.getCourse(course_id_string, true);
let lecture = course.lectures!.find((l) => l.id === parseInt(lecture_id));
if (lecture === undefined) {
throw new Error("Lecture not found");
}
let perms: AuthenticationStatusResponse = {
authenticated_methods: ["public"],
is_lecture_authenticated: true,
};
if (
lecture.authentication_methods.includes("public") === false &&
lecture.authentication_methods.length > 0
) {
perms = await api.getAuthenticationStatus({ lecture_id: lecture.id });
}
return { course, perms, loaderHadUserInfo: hasUserInfo, lectureId: parseInt(lecture_id) };
}
// This redirector handles both the regular player and the embedded player
export function ActualRedirector() {
const api = useBackendContext();
const userContext = useUserContext();
const hasUserInfo = userContext.hasUserInfo();
const pathname = usePathname();
const matchedParamsPlayer = pathname.match(/^\/([a-z0-9_-]+)\/([a-z0-9_-]+)$/);
const matchedParamsEmbed = pathname.match(/^\/([a-z0-9_-]+)\/([a-z0-9_-]+)\/embed$/);
const [playerData, setPlayerData] = useState<ResourceType<PlayerData>>();
const reloadData = () => {
if (!matchedParamsPlayer && !matchedParamsEmbed) return;
const matched = (matchedParamsPlayer ?? matchedParamsEmbed)!;
const courseId = matched[1];
const lectureId = matched[2];
setPlayerData(
fetchDataWrapper(dataLoader(api, courseId, lectureId, userContext.hasUserInfo())),
);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(reloadData, [api, hasUserInfo, pathname]);
if (!matchedParamsPlayer && !matchedParamsEmbed)
return <Four04 title="Unbekannter Pfad" text="dynamic_player" />;
if (matchedParamsPlayer && matchedParamsEmbed)
throw new Error("Both matchedParamsPlayer and matchedParamsEmbed are true");
if (!playerData) return <></>;
function Custom404() {
const error = useRouteError();
return (
<ReloadBoundary reloadFunc={reloadData}>
<FallbackErrorBoundary
fallback={(e: any) => (
<ErrorPage
error={error}
error={e}
objectName="Vorlesung"
expectedErrorCodes={[
"unauthorized",
......@@ -21,40 +87,13 @@ function Custom404() {
"rate_limited",
]}
/>
);
}
function ActualRedirector() {
const api = useBackendContext();
const userContext = useUserContext();
const router = createBrowserRouter(
[
{
path: ":course_id_string/:lecture_id",
loader: getLectureDataLoader(api, userContext.hasUserInfo()),
errorElement: <Custom404 />,
element: <Player />,
},
{
path: "404",
element: <Four04 />,
},
{
path: "*",
element: <>* matched dynamic_player</>,
},
],
{ basename: nextConfig.basePath },
);
const reload = () => {
router.navigate(".", { replace: true });
};
return (
<ReloadBoundary reloadFunc={reload}>
<RouterProvider router={router} />
)}
>
<Suspense fallback={<div className="spinner-border" />}>
{matchedParamsEmbed && <EmbeddedPlayer playerData={playerData} />}
{matchedParamsPlayer && <Player playerData={playerData} />}
</Suspense>
</FallbackErrorBoundary>
</ReloadBoundary>
);
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment