Select Git revision
meetings.py
Forked from
Video AG Infrastruktur / website
Source project has a limited visibility.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
index.tsx 11.82 KiB
import { useBackendContext } from "@/components/BackendProvider";
import { useEditMode } from "@/components/EditModeProvider";
import { FallbackErrorBoundary } from "@/components/FallbackErrorBoundary";
import {
OMCreate,
OMDelete,
OMEdit,
OMHistory,
EmbeddedOMFieldComponent,
} from "@/components/OMConfigComponent";
import { ReloadBoundary, useReloadBoundary } from "@/components/ReloadBoundary";
import { useUserContext } from "@/components/UserDataProvider";
import { ResourceType, fetchDataWrapper } from "@/misc/PromiseHelpers";
import React from "react";
import { ErrorPage, showError, showWarningToast } from "@/misc/ErrorHandlers";
import { Suspense, useEffect, useState } from "react";
import type { GetHomepageResponse, featured, int } from "@/api/api_v1_types";
import { useLanguage } from "@/components/LanguageProvider";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import { VideoCard } from "@/components/VideoCard";
import { LectureLiveLabel } from "@/components/LiveLabel";
function FeatureCard({ data, all_featured }: { data: featured; all_featured: featured[] }) {
const { editMode } = useEditMode();
const { language } = useLanguage();
const reloadFunc = useReloadBoundary();
const api = useBackendContext();
const isFirst = all_featured[0].id === data.id;
const isLast = all_featured[all_featured.length - 1].id === data.id;
const movePanel = (dir: int) => {
const myIndex = all_featured.findIndex((f) => f.id === data.id);
api.updateOMObject("featured", data.id!, {
updates: {
display_priority: dir == -1 ? myIndex - 2 : myIndex + 1,
},
expected_current_values: {
display_priority: myIndex,
},
})
.then(reloadFunc)
.catch((error) => {
showError(error, "Error while moving panel");
});
};
let bgColor = "";
if (data.is_visible === false) {
bgColor = "bg-danger-subtle";
}
return (
<div className={"card mb-3 " + bgColor}>
<div className="card-body">
<h5 className="card-title d-flex align-items-center">
<div className="overflow-hidden pb-1">
<EmbeddedOMFieldComponent
object_type="featured"
object_id={data.id!}
field_id="title"
field_type="string"
initialValue={data.title}
allowMarkdown={true}
/>
</div>
{editMode && (
<EmbeddedOMFieldComponent
object_type="featured"
object_id={data.id!}
field_id="is_visible"
field_type="boolean"
initialValue={data.is_visible}
/>
)}
<div className="flex-fill" />
{editMode && (
<div>
<OMHistory object_type="featured" object_id={data.id!} />
<div className="btn-group m-1" role="group">
<button
type="button"
className="btn btn-outline-info"
disabled={isFirst}
onClick={() => movePanel(-1)}
>
<i className="bi bi-arrow-up" />
</button>
<button
type="button"
className="btn btn-outline-info"
disabled={isLast}
onClick={() => movePanel(1)}
>
<i className="bi bi-arrow-down" />
</button>
</div>
<OMEdit object_type="featured" object_id={data.id!} />
<OMDelete object_type="featured" object_id={data.id!} />
</div>
)}
</h5>
<EmbeddedOMFieldComponent
object_type="featured"
object_id={data.id!}
field_id="text"
field_type="string"
initialValue={data.text}
className="flex-fill"
allowMarkdown={true}
/>
{data.is_visible === false && (
<>
<hr />
<small className="text-muted">
{language.get("ui.generic.object_invisible")}
</small>
</>
)}
</div>
</div>
);
}
function FeaturedContent({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
const data = homepageData.read()!;
return data.featured.map((f) => (
<FeatureCard key={f.title} data={f} all_featured={data.featured} />
));
}
function UpcomingUploads({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
const data = homepageData.read()!;
const { language } = useLanguage();
if (data.upcoming_lectures.length === 0) {
return (
<span className="disable-last-paragraph-spacing">
{language.getStyled("ui.index.no_scheduled_recordings", true)}
</span>
);
}
return (
<ul className="list-group">
{data.upcoming_lectures.map((lecture, index) => {
let date_parsed = new Date(lecture.time);
let course_data = data.courses_context[lecture.course_id];
return (
<li className="list-group-item" key={index}>
<strong>
{
//TODO splitting the date and time only makes sense, if the same dates are grouped
// format: Mo, 29.01.2024
date_parsed.toLocaleDateString([], {
weekday: "short",
year: "numeric",
month: "2-digit",
day: "2-digit",
})
}
</strong>
<ul className="list-group">
<li
className="list-group-item list-group-item-condensed"
style={{ border: "none" }}
>
{
// only time (HH:MM)
date_parsed.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})
}{" "}
<a href={`/${course_data.id_string}`}>{course_data.full_name}</a>
{": "}
<a href={`/${course_data.id_string}#lecture-${lecture.id}`}>
{lecture.title}
</a>
{lecture.location.length > 0 && ` (${lecture.location})`}{" "}
<LectureLiveLabel lecture={lecture} />
</li>
</ul>
</li>
);
})}
</ul>
);
}
function RecentUploads({ homepageData }: { homepageData: ResourceType<GetHomepageResponse> }) {
const data = homepageData.read()!;
return (
<ul className="list-group videopreview">
{data.new_lectures.map((lecture, index) => {
let course_data = data.courses_context[lecture.course_id];
return (
<li className="list-group-item" key={index}>
<VideoCard lecture={lecture} course={course_data} />{" "}
</li>
);
})}
</ul>
);
}
function ModButtons() {
const { language } = useLanguage();
return (
<div className="d-flex mb-2">
<div className="flex-fill" />
<OMCreate object_type="featured">
<button className="btn btn-secondary" type="button">
<i className="bi bi-plus" />
{language.get("ui.index.create_panel")}
</button>
</OMCreate>
</div>
);
}
export default function Home() {
const api = useBackendContext();
const userContext = useUserContext();
const [homepageData, setHomepageData] = useState<ResourceType<GetHomepageResponse>>();
const hasUserInfo = userContext.hasUserInfo();
const { editMode } = useEditMode();
const { language } = useLanguage();
// legacy redirects
const searchParams = useSearchParams();
const router = useRouter();
const handleLegacyRedirects = () => {
if (searchParams.has("course")) {
router.replace(`/${searchParams.get("course")}`);
return;
}
if (searchParams.has("view")) {
if (searchParams.get("view") === "player") {
showWarningToast(
"Die URL-Struktur hat sich geändert. Bitte finden sie ihr gesuchtes Video über die Suchfunktion.",
);
}
if (searchParams.get("view") === "faq") {
router.replace("/faq");
return;
}
}
};
useEffect(handleLegacyRedirects, [searchParams, router]);
const reloadData = () => {
setHomepageData(fetchDataWrapper(api.getHomepage()));
};
useEffect(reloadData, [api, hasUserInfo]);
if (homepageData === undefined) return <></>;
return (
<div className="row">
<ReloadBoundary reloadFunc={reloadData}>
<FallbackErrorBoundary
fallback={(e: any) => <ErrorPage error={e} objectName="Seite" />}
>
{editMode && <ModButtons />}
<Suspense fallback={<div className="spinner-border" />}>
<div className="col-md-6">
<FeaturedContent homepageData={homepageData} />
</div>
<div className="col-md-6">
<div className="card mb-3">
<div className="card-body">
<h5 className="card-title">
{language.get("ui.index.next_recordings")}
</h5>
<UpcomingUploads homepageData={homepageData} />
</div>
</div>
<div className="card mb-3">
<div className="card-body">
<h5 className="card-title">
{language.get("ui.index.recent_videos")}
</h5>
<RecentUploads homepageData={homepageData} />
</div>
</div>
</div>
</Suspense>
</FallbackErrorBoundary>
</ReloadBoundary>
</div>
);
}