aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/pages/timeline/Timeline.tsx
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-07-30 23:47:53 +0800
committercrupest <crupest@outlook.com>2023-07-30 23:47:53 +0800
commit538d6830a0022b49b99695095d85e567b0c86e71 (patch)
treea0c4d164b05d03f636d603b28f77ca881c16ef10 /FrontEnd/src/pages/timeline/Timeline.tsx
parenta148f11c193d35ba489f887ed393aedf58a1c714 (diff)
downloadtimeline-538d6830a0022b49b99695095d85e567b0c86e71.tar.gz
timeline-538d6830a0022b49b99695095d85e567b0c86e71.tar.bz2
timeline-538d6830a0022b49b99695095d85e567b0c86e71.zip
...
Diffstat (limited to 'FrontEnd/src/pages/timeline/Timeline.tsx')
-rw-r--r--FrontEnd/src/pages/timeline/Timeline.tsx207
1 files changed, 207 insertions, 0 deletions
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<TimelineProps> = (props) => {
+ const { timelineOwner, timelineName, className, style } = props;
+
+ const user = useUser();
+
+ const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null);
+ const [posts, setPosts] = React.useState<HttpTimelinePostInfo[] | null>(null);
+ const [signalrState, setSignalrState] = React.useState<HubConnectionState>(
+ 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 (
+ <div className={className} style={style}>
+ Offline.
+ </div>
+ );
+ } else if (error === "notfound") {
+ return (
+ <div className={className} style={style}>
+ Not exist.
+ </div>
+ );
+ } else if (error === "forbid") {
+ return (
+ <div className={className} style={style}>
+ Forbid.
+ </div>
+ );
+ } else if (error === "error") {
+ return (
+ <div className={className} style={style}>
+ Error.
+ </div>
+ );
+ }
+ return (
+ <>
+ {timeline == null && posts == null && <TimelineLoading />}
+ {timeline && (
+ <TimelineCard
+ className="timeline-card"
+ timeline={timeline}
+ connectionStatus={signalrState}
+ onReload={updateTimeline}
+ />
+ )}
+ {posts && (
+ <div style={style} className={classnames("timeline", className)}>
+ <TimelineEmptyItem className="timeline-top" height={50} />
+ {timeline?.postable ? (
+ <TimelinePostEdit timeline={timeline} onPosted={updatePosts} />
+ ) : user == null ? (
+ <TimelinePostEditNoLogin />
+ ) : null}
+ <TimelinePostListView posts={posts} onReload={updatePosts} />
+ </div>
+ )}
+ </>
+ );
+};
+
+export default Timeline;