diff options
Diffstat (limited to 'FrontEnd/src/views/timeline/Timeline.tsx')
-rw-r--r-- | FrontEnd/src/views/timeline/Timeline.tsx | 216 |
1 files changed, 121 insertions, 95 deletions
diff --git a/FrontEnd/src/views/timeline/Timeline.tsx b/FrontEnd/src/views/timeline/Timeline.tsx index e8b1147f..6399b6bc 100644 --- a/FrontEnd/src/views/timeline/Timeline.tsx +++ b/FrontEnd/src/views/timeline/Timeline.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { HubConnectionState } from "@microsoft/signalr"; import classnames from "classnames"; +import { HubConnectionState } from "@microsoft/signalr"; import { HttpForbiddenError, @@ -13,16 +13,15 @@ import { HttpTimelinePostInfo, } from "@/http/timeline"; -import { getTimelinePostUpdate$ } from "@/services/timeline"; import { useUser } from "@/services/user"; - -import useValueWithRef from "@/utilities/useValueWithRef"; +import { getTimelinePostUpdate$ } from "@/services/timeline"; import TimelinePagedPostListView from "./TimelinePagedPostListView"; import TimelineEmptyItem from "./TimelineEmptyItem"; import TimelineLoading from "./TimelineLoading"; import TimelinePostEdit from "./TimelinePostEdit"; import TimelinePostEditNoLogin from "./TimelinePostEditNoLogin"; +import TimelineCard from "./TimelineCard"; import "./index.css"; @@ -31,143 +30,170 @@ export interface TimelineProps { style?: React.CSSProperties; timelineOwner: string; timelineName: string; - reloadKey: number; - onReload: () => void; - onTimelineLoaded?: (timeline: HttpTimelineInfo) => void; - onConnectionStateChanged?: (state: HubConnectionState) => void; } const Timeline: React.FC<TimelineProps> = (props) => { - const { timelineOwner, timelineName, className, style, reloadKey } = props; + const { timelineOwner, timelineName, className, style } = props; const user = useUser(); - const [state, setState] = React.useState< - "loading" | "loaded" | "offline" | "notexist" | "forbid" | "error" - >("loading"); const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null); - const [posts, setPosts] = React.useState<HttpTimelinePostInfo[]>([]); + 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 [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(() => { - setState("loading"); setTimeline(null); - setPosts([]); - }, [timelineName]); - - const onReload = useValueWithRef(props.onReload); - const onTimelineLoaded = useValueWithRef(props.onTimelineLoaded); - const onConnectionStateChanged = useValueWithRef( - props.onConnectionStateChanged - ); + setPosts(null); + setError(null); + setSignalrState(HubConnectionState.Connecting); + }, [timelineOwner, timelineName]); React.useEffect(() => { - if (timelineName != null && state === "loaded") { - const timelinePostUpdate$ = getTimelinePostUpdate$( - timelineOwner, - timelineName - ); - const subscription = timelinePostUpdate$.subscribe( - ({ update, state }) => { - if (update) { - onReload.current(); + if (timelineName != null) { + let subscribe = true; + + getHttpTimelineClient() + .getTimeline(timelineOwner, timelineName) + .then( + (t) => { + if (subscribe) { + setTimeline(t); + } + }, + (error) => { + if (subscribe) { + 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"); + } + } } - onConnectionStateChanged.current?.(state); - } - ); + ); + return () => { - subscription.unsubscribe(); + subscribe = false; }; } - }, [timelineOwner, timelineName, state, onReload, onConnectionStateChanged]); + }, [timelineOwner, timelineName, timelineReloadKey]); React.useEffect(() => { - if (timelineName != null) { - let subscribe = true; - - const client = getHttpTimelineClient(); - Promise.all([ - client.getTimeline(timelineOwner, timelineName), - client.listPost(timelineOwner, timelineName), - ]).then( - ([t, p]) => { + let subscribe = true; + void getHttpTimelineClient() + .listPost(timelineOwner, timelineName) + .then( + (ps) => { if (subscribe) { - setTimeline(t); setPosts( - p.items.filter( + ps.items.filter( (p): p is HttpTimelinePostInfo => p.deleted === false ) ); - setState("loaded"); - onTimelineLoaded.current?.(t); } }, (error) => { if (subscribe) { if (error instanceof HttpNetworkError) { - setState("offline"); + setError("offline"); } else if (error instanceof HttpForbiddenError) { - setState("forbid"); + setError("forbid"); } else if (error instanceof HttpNotFoundError) { - setState("notexist"); + setError("notfound"); } else { console.error(error); - setState("error"); + setError("error"); } } } ); + return () => { + subscribe = false; + }; + }, [timelineOwner, timelineName, postsReloadKey]); - return () => { - subscribe = false; - }; - } - }, [timelineOwner, timelineName, reloadKey, onTimelineLoaded]); - - switch (state) { - case "loading": - return <TimelineLoading />; - case "offline": - return ( - <div className={className} style={style}> - Offline. - </div> - ); - case "notexist": - return ( - <div className={className} style={style}> - Not exist. - </div> - ); - case "forbid": - return ( - <div className={className} style={style}> - Forbid. - </div> - ); - case "error": - return ( - <div className={className} style={style}> - Error. - </div> - ); - default: - return ( + 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]); + + 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 height={40} /> - <TimelinePagedPostListView - posts={posts} - onReload={onReload.current} - /> + <TimelinePagedPostListView posts={posts} onReload={updatePosts} /> {timeline?.postable ? ( - <TimelinePostEdit timeline={timeline} onPosted={onReload.current} /> + <TimelinePostEdit timeline={timeline} onPosted={updatePosts} /> ) : user == null ? ( <TimelinePostEditNoLogin /> ) : ( <TimelineEmptyItem startSegmentLength={20} center="none" current /> )} </div> - ); - } + )} + </> + ); }; export default Timeline; |