Skip to content
Snippets Groups Projects
Select Git revision
  • c61daa81392c15b41fdc0cfb81b76a4f4adf68a8
  • master default protected
  • forbid-save-as
  • upload-via-token
  • moodle-integration
  • patch-double-tap-seek
  • patch_datum_anzeigen
  • patch_raum_anzeigen
  • intros
  • live_sources
  • bootstrap4
  • modules
12 results

meetings.py

Blame
  • 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>
        );
    }