import React from "react"; import { useTranslation } from "react-i18next"; import { UiLogicError } from "@/common"; import { pushAlert } from "@/services/alert"; import { useUser } from "@/services/user"; import { timelineService, usePosts, useTimeline } from "@/services/timeline"; import { mergeDataStatus } from "@/services/DataHub2"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import { TimelinePageTemplateData, TimelinePageTemplateUIProps, } from "./TimelinePageTemplateUI"; import { TimelinePostInfoEx } from "./Timeline"; export interface TimelinePageTemplateProps { name: string; onManage: (item: TManageItem) => void; UiComponent: React.ComponentType< Omit, "CardComponent"> >; notFoundI18nKey: string; } export default function TimelinePageTemplate( props: TimelinePageTemplateProps ): React.ReactElement | null { const { t } = useTranslation(); const { name } = props; const service = timelineService; const user = useUser(); const [dialog, setDialog] = React.useState( null ); const [scrollBottomKey, setScrollBottomKey] = React.useState(0); React.useEffect(() => { if (scrollBottomKey > 0) { window.scrollTo(0, document.body.scrollHeight); } }, [scrollBottomKey]); const timelineAndStatus = useTimeline(name); const postsAndState = usePosts(name); const [ scrollToBottomNextSyncKey, setScrollToBottomNextSyncKey, ] = React.useState(0); const scrollToBottomNextSync = (): void => { setScrollToBottomNextSyncKey((old) => old + 1); }; React.useEffect(() => { let subscribe = true; void timelineService.syncPosts(name).then(() => { if (subscribe) { setScrollBottomKey((old) => old + 1); } }); return () => { subscribe = false; }; }, [name, scrollToBottomNextSyncKey]); const data = ((): TimelinePageTemplateUIProps["data"] => { const { status, data: timeline } = timelineAndStatus; if (timeline == null) { if (status === "offline") { return { type: "custom", value: "Network Error" }; } else { return undefined; } } else if (timeline === "notexist") { return props.notFoundI18nKey; } else { const posts = ((): TimelinePostInfoEx[] | "forbid" | undefined => { const { data: postsInfo } = postsAndState; if (postsInfo === "forbid") { return "forbid"; } else if (postsInfo == null || postsInfo === "notexist") { return undefined; } else { return postsInfo.posts.map((post) => ({ ...post, onDelete: service.hasModifyPostPermission(user, timeline, post) ? () => { service.deletePost(name, post.id).catch(() => { pushAlert({ type: "danger", message: t("timeline.deletePostFailed"), }); }); } : undefined, })); } })(); const operations: TimelinePageTemplateData["operations"] = { onPost: service.hasPostPermission(user, timeline) ? (req) => service.createPost(name, req).then(() => scrollToBottomNextSync()) : undefined, onManage: service.hasManagePermission(user, timeline) ? (item) => { if (item === "property") { setDialog(item); } else { props.onManage(item); } } : undefined, onMember: () => setDialog("member"), onBookmark: user != null ? () => { service .setBookmark(timeline.name, !timeline.isBookmark) .catch(() => { pushAlert({ message: { type: "i18n", key: timeline.isBookmark ? "timeline.removeBookmarkFail" : "timeline.addBookmarkFail", }, type: "danger", }); }); } : undefined, onHighlight: user != null && user.hasHighlightTimelineAdministrationPermission ? () => { service .setHighlight(timeline.name, !timeline.isHighlight) .catch(() => { pushAlert({ message: { type: "i18n", key: timeline.isHighlight ? "timeline.removeHighlightFail" : "timeline.addHighlightFail", }, type: "danger", }); }); } : undefined, }; return { timeline, posts, operations }; } })(); const timeline = timelineAndStatus?.data; let dialogElement: React.ReactElement | undefined; const closeDialog = (): void => setDialog(null); if (dialog === "property") { if (timeline == null || timeline === "notexist") { throw new UiLogicError( "Timeline is null but attempt to open change property dialog." ); } dialogElement = ( service.changeTimelineProperty(name, req)} /> ); } else if (dialog === "member") { if (timeline == null || timeline === "notexist") { throw new UiLogicError( "Timeline is null but attempt to open change property dialog." ); } dialogElement = ( ); } const { UiComponent } = props; return ( <> {dialogElement} ); }