import React from "react"; import { useTranslation } from "react-i18next"; import { Container } from "react-bootstrap"; import { HubConnectionState } from "@microsoft/signalr"; import { HttpNetworkError, HttpNotFoundError } from "@/http/common"; import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; import { getAlertHost } from "@/services/alert"; import Timeline from "./Timeline"; import TimelinePostEdit from "./TimelinePostEdit"; import useReverseScrollPositionRemember from "@/utilities/useReverseScrollPositionRemember"; import { generatePalette, setPalette } from "@/palette"; export interface TimelinePageCardProps { timeline: HttpTimelineInfo; collapse: boolean; toggleCollapse: () => void; connectionStatus: HubConnectionState; className?: string; onReload: () => void; } export interface TimelinePageTemplateProps { timelineName: string; notFoundI18nKey: string; reloadKey: number; onReload: () => void; CardComponent: React.ComponentType; } const TimelinePageTemplate: React.FC = (props) => { const { timelineName, reloadKey, onReload, CardComponent } = props; const { t } = useTranslation(); const [state, setState] = React.useState< "loading" | "done" | "offline" | "notexist" | "error" >("loading"); const [timeline, setTimeline] = React.useState(null); const [connectionStatus, setConnectionStatus] = React.useState(HubConnectionState.Connecting); useReverseScrollPositionRemember(); React.useEffect(() => { setState("loading"); setTimeline(null); }, [timelineName]); React.useEffect(() => { let subscribe = true; void getHttpTimelineClient() .getTimeline(timelineName) .then( (data) => { if (subscribe) { setState("done"); setTimeline(data); } }, (error) => { if (subscribe) { if (error instanceof HttpNetworkError) { setState("offline"); } else if (error instanceof HttpNotFoundError) { setState("notexist"); } else { console.error(error); setState("error"); } setTimeline(null); } } ); return () => { subscribe = false; }; }, [timelineName, reloadKey]); React.useEffect(() => { if (timeline != null && timeline.color != null) { return setPalette(generatePalette({ primary: timeline.color })); } }, [timeline]); const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState(0); const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); const reloadTimeline = (): void => { setTimelineReloadKey((old) => old + 1); }; const onPostEditHeightChange = React.useCallback((height: number): void => { setBottomSpaceHeight(height); if (height === 0) { const alertHost = getAlertHost(); if (alertHost != null) { alertHost.style.removeProperty("margin-bottom"); } } else { const alertHost = getAlertHost(); if (alertHost != null) { alertHost.style.marginBottom = `${height}px`; } } }, []); const cardCollapseLocalStorageKey = `timeline.${timelineName}.cardCollapse`; const [cardCollapse, setCardCollapse] = React.useState(true); React.useEffect(() => { const savedCollapse = window.localStorage.getItem( cardCollapseLocalStorageKey ); setCardCollapse(savedCollapse == null ? true : savedCollapse === "true"); }, [cardCollapseLocalStorageKey]); const toggleCardCollapse = (): void => { const newState = !cardCollapse; setCardCollapse(newState); window.localStorage.setItem( cardCollapseLocalStorageKey, newState.toString() ); }; return ( <> {timeline != null ? ( ) : null} {(() => { if (state === "offline") { // TODO: i18n return

Offline!

; } else if (state === "notexist") { return

{t(props.notFoundI18nKey)}

; } else if (state === "error") { // TODO: i18n return

Error!

; } else { return ( ); } })()}
{timeline != null && timeline.postable ? ( <>
) : null} ); }; export default TimelinePageTemplate;