From fee641197dc4359c189b9ebea45800d71cb5aa8d Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 24 Apr 2022 16:37:09 +0800 Subject: ... --- FrontEnd/src/views/timeline/Timeline.tsx | 216 +++++++++++++++------------ FrontEnd/src/views/timeline/TimelineCard.tsx | 16 +- FrontEnd/src/views/timeline/index.css | 2 +- FrontEnd/src/views/timeline/index.tsx | 72 +-------- 4 files changed, 134 insertions(+), 172 deletions(-) (limited to 'FrontEnd/src/views') 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 = (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(null); - const [posts, setPosts] = React.useState([]); + 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 [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 ; - case "offline": - return ( -
- Offline. -
- ); - case "notexist": - return ( -
- Not exist. -
- ); - case "forbid": - return ( -
- Forbid. -
- ); - case "error": - return ( -
- Error. -
- ); - 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 ( +
+ 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 ? ( ) : ( )}
- ); - } + )} + + ); }; export default Timeline; diff --git a/FrontEnd/src/views/timeline/TimelineCard.tsx b/FrontEnd/src/views/timeline/TimelineCard.tsx index 872ad6d3..08eae3e0 100644 --- a/FrontEnd/src/views/timeline/TimelineCard.tsx +++ b/FrontEnd/src/views/timeline/TimelineCard.tsx @@ -22,22 +22,13 @@ import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; export interface TimelinePageCardProps { timeline: HttpTimelineInfo; - collapse: boolean; - toggleCollapse: () => void; connectionStatus: HubConnectionState; className?: string; onReload: () => void; } const TimelineCard: React.FC = (props) => { - const { - timeline, - collapse, - toggleCollapse, - connectionStatus, - onReload, - className, - } = props; + const { timeline, connectionStatus, onReload, className } = props; const { t } = useTranslation(); @@ -45,6 +36,11 @@ const TimelineCard: React.FC = (props) => { "member" | "property" | "delete" | null >(null); + const [collapse, setCollapse] = React.useState(false); + const toggleCollapse = (): void => { + setCollapse((o) => !o); + }; + const isSmallScreen = useIsSmallScreen(); const user = useUser(); diff --git a/FrontEnd/src/views/timeline/index.css b/FrontEnd/src/views/timeline/index.css index 6929f9ae..fa3542dd 100644 --- a/FrontEnd/src/views/timeline/index.css +++ b/FrontEnd/src/views/timeline/index.css @@ -237,7 +237,7 @@ margin-right: 0.6em; } -.timeline-template-card { +.timeline-card { position: fixed; z-index: 1029; top: 56px; diff --git a/FrontEnd/src/views/timeline/index.tsx b/FrontEnd/src/views/timeline/index.tsx index 65bb90f6..cb9fb46f 100644 --- a/FrontEnd/src/views/timeline/index.tsx +++ b/FrontEnd/src/views/timeline/index.tsx @@ -1,82 +1,22 @@ import React from "react"; -import { HubConnectionState } from "@microsoft/signalr"; import { useParams } from "react-router-dom"; import { UiLogicError } from "@/common"; -import { HttpTimelineInfo } from "@/http/timeline"; -import { generatePalette, setPalette } from "@/palette"; import Timeline from "./Timeline"; -import TimelineCard from "./TimelineCard"; const TimelinePage: React.FC = () => { - const { owner: ownerUsername, timeline: timelineNameParam } = useParams(); + const { owner, timeline: timelineNameParam } = useParams(); - if (ownerUsername == null || ownerUsername == "") + if (owner == null || owner == "") throw new UiLogicError("Route param owner is not set."); - const timelineName = - timelineNameParam == null || timelineNameParam === "" - ? "self" - : timelineNameParam; - - const [timeline, setTimeline] = React.useState(null); - - const [reloadKey, setReloadKey] = React.useState(0); - const reload = (): void => setReloadKey(reloadKey + 1); - - const [connectionStatus, setConnectionStatus] = - React.useState(HubConnectionState.Connecting); - - React.useEffect(() => { - if (timeline != null && timeline.color != null) { - return setPalette(generatePalette({ primary: timeline.color })); - } - }, [timeline]); - - const cardCollapseLocalStorageKey = `timeline.${ownerUsername}.${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() - ); - }; + const timeline = timelineNameParam || "self"; return ( - <> - {timeline != null ? ( - - ) : null} -
- setTimeline(t)} - onConnectionStateChanged={setConnectionStatus} - /> -
- +
+ +
); }; -- cgit v1.2.3