From 538d6830a0022b49b99695095d85e567b0c86e71 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 30 Jul 2023 23:47:53 +0800 Subject: ... --- FrontEnd/src/pages/timeline/Timeline.tsx | 207 +++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 FrontEnd/src/pages/timeline/Timeline.tsx (limited to 'FrontEnd/src/pages/timeline/Timeline.tsx') diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx new file mode 100644 index 00000000..3a7fbd00 --- /dev/null +++ b/FrontEnd/src/pages/timeline/Timeline.tsx @@ -0,0 +1,207 @@ +import * as React from "react"; +import classnames from "classnames"; +import { useScrollToBottom } from "@/utilities/hooks"; +import { HubConnectionState } from "@microsoft/signalr"; + +import { + HttpForbiddenError, + HttpNetworkError, + HttpNotFoundError, +} from "@/http/common"; +import { + getHttpTimelineClient, + HttpTimelineInfo, + HttpTimelinePostInfo, +} from "@/http/timeline"; + +import { useUser } from "@/services/user"; +import { getTimelinePostUpdate$ } from "@/services/timeline"; + +import TimelinePostListView from "./TimelinePostListView"; +import TimelineEmptyItem from "./TimelineEmptyItem"; +import TimelineLoading from "./TimelineLoading"; +import TimelinePostEdit from "./TimelinePostEdit"; +import TimelinePostEditNoLogin from "./TimelinePostEditNoLogin"; +import TimelineCard from "./TimelineCard"; + +import "./Timeline.css"; + +export interface TimelineProps { + className?: string; + style?: React.CSSProperties; + timelineOwner: string; + timelineName: string; +} + +const Timeline: React.FC = (props) => { + const { timelineOwner, timelineName, className, style } = props; + + const user = useUser(); + + const [timeline, setTimeline] = React.useState(null); + const [posts, setPosts] = React.useState(null); + const [signalrState, setSignalrState] = React.useState( + HubConnectionState.Connecting + ); + const [error, setError] = React.useState< + "offline" | "forbid" | "notfound" | "error" | null + >(null); + + const [currentPage, setCurrentPage] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(0); + + const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); + const [postsReloadKey, setPostsReloadKey] = React.useState(0); + + const updateTimeline = (): void => setTimelineReloadKey((o) => o + 1); + const updatePosts = (): void => setPostsReloadKey((o) => o + 1); + + React.useEffect(() => { + setTimeline(null); + setPosts(null); + setError(null); + setSignalrState(HubConnectionState.Connecting); + }, [timelineOwner, timelineName]); + + React.useEffect(() => { + getHttpTimelineClient() + .getTimeline(timelineOwner, timelineName) + .then( + (t) => { + setTimeline(t); + }, + (error) => { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); + } + } + ); + }, [timelineOwner, timelineName, timelineReloadKey]); + + React.useEffect(() => { + getHttpTimelineClient() + .listPost(timelineOwner, timelineName, 1) + .then( + (page) => { + setPosts( + page.items.filter((p): p is HttpTimelinePostInfo => !p.deleted) + ); + setTotalPage(page.totalPageCount); + }, + (error) => { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); + } + } + ); + }, [timelineOwner, timelineName, postsReloadKey]); + + React.useEffect(() => { + const timelinePostUpdate$ = getTimelinePostUpdate$( + timelineOwner, + timelineName + ); + const subscription = timelinePostUpdate$.subscribe(({ update, state }) => { + if (update) { + setPostsReloadKey((o) => o + 1); + } + setSignalrState(state); + }); + return () => { + subscription.unsubscribe(); + }; + }, [timelineOwner, timelineName]); + + useScrollToBottom(() => { + console.log(`Load page ${currentPage + 1}.`); + setCurrentPage(currentPage + 1); + void getHttpTimelineClient() + .listPost(timelineOwner, timelineName, currentPage + 1) + .then( + (page) => { + const ps = page.items.filter( + (p): p is HttpTimelinePostInfo => !p.deleted + ); + setPosts((old) => [...(old ?? []), ...ps]); + }, + (error) => { + if (error instanceof HttpNetworkError) { + setError("offline"); + } else if (error instanceof HttpForbiddenError) { + setError("forbid"); + } else if (error instanceof HttpNotFoundError) { + setError("notfound"); + } else { + console.error(error); + setError("error"); + } + } + ); + }, currentPage < totalPage); + + if (error === "offline") { + return ( +
+ Offline. +
+ ); + } else if (error === "notfound") { + return ( +
+ Not exist. +
+ ); + } else if (error === "forbid") { + return ( +
+ Forbid. +
+ ); + } else if (error === "error") { + return ( +
+ Error. +
+ ); + } + return ( + <> + {timeline == null && posts == null && } + {timeline && ( + + )} + {posts && ( +
+ + {timeline?.postable ? ( + + ) : user == null ? ( + + ) : null} + +
+ )} + + ); +}; + +export default Timeline; -- cgit v1.2.3