From eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 11 Nov 2020 21:02:15 +0800 Subject: refactor: Refactor timeline props. --- .../src/app/views/timeline-common/Timeline.tsx | 9 +- .../views/timeline-common/TimelinePageTemplate.tsx | 153 ++++++++++++------- .../timeline-common/TimelinePageTemplateUI.tsx | 170 +++++++-------------- 3 files changed, 158 insertions(+), 174 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx index ff9f663a..aba868cb 100644 --- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx @@ -7,7 +7,7 @@ import TimelineItem from "./TimelineItem"; import TimelineTop from "./TimelineTop"; export interface TimelinePostInfoEx extends TimelinePostInfo { - deletable: boolean; + onDelete?: () => void; } export type TimelineDeleteCallback = (index: number, id: number) => void; @@ -16,13 +16,12 @@ export interface TimelineProps { className?: string; style?: React.CSSProperties; posts: TimelinePostInfoEx[]; - onDelete: TimelineDeleteCallback; onResize?: () => void; containerRef?: React.Ref; } const Timeline: React.FC = (props) => { - const { posts, onDelete, onResize } = props; + const { posts, onResize } = props; const [showMoreIndex, setShowMoreIndex] = React.useState(-1); @@ -42,12 +41,12 @@ const Timeline: React.FC = (props) => { key={post.id} current={length - 1 === index} more={ - post.deletable + post.onDelete != null ? { isOpen: showMoreIndex === index, toggle: () => setShowMoreIndex((old) => (old === index ? -1 : index)), - onDelete: () => onDelete(index, post.id), + onDelete: post.onDelete, } : undefined } diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 0f792b53..6c57e91d 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -12,11 +12,12 @@ import { useTimelineInfo, } from "@/services/timeline"; -import { TimelineDeleteCallback } from "./Timeline"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import { TimelinePageTemplateUIProps } from "./TimelinePageTemplateUI"; import { TimelinePostSendCallback } from "./TimelinePostEdit"; +import { TimelineSyncStatus } from "./SyncStatusBadge"; +import { TimelinePostInfoEx } from "./Timeline"; export interface TimelinePageTemplateProps { name: string; @@ -43,19 +44,101 @@ export default function TimelinePageTemplate( ); const timelineState = useTimelineInfo(name); + const postListState = usePostList(name); - const timeline = timelineState?.timeline; + const onPost: TimelinePostSendCallback = React.useCallback( + (req) => { + return service.createPost(name, req).toPromise().then(); + }, + [service, name] + ); - const postListState = usePostList(name); + const onManageProp = props.onManage; - const error: string | undefined = (() => { - if (timelineState != null) { + const onManage = React.useCallback( + (item: "property" | TManageItem) => { + if (item === "property") { + setDialog(item); + } else { + onManageProp(item); + } + }, + [onManageProp] + ); + + const childProps = ((): [ + data: TimelinePageTemplateUIProps["data"], + syncStatus: TimelineSyncStatus + ] => { + if (timelineState == null) { + return [undefined, "syncing"]; + } else { const { type, timeline } = timelineState; - if (type === "offline" && timeline == null) return "Network Error"; - if (type === "synced" && timeline == null) - return t(props.notFoundI18nKey); + if (timeline == null) { + if (type === "offline") { + return [{ type: "custom", value: "Network Error" }, "offline"]; + } else if (type === "synced") { + return [props.notFoundI18nKey, "synced"]; + } else { + return [undefined, "syncing"]; + } + } else { + if (postListState != null && postListState.type === "notexist") { + return [props.notFoundI18nKey, "synced"]; + } + if (postListState != null && postListState.type === "forbid") { + return ["timeline.messageCantSee", "synced"]; + } + + const posts: + | TimelinePostInfoEx[] + | undefined = postListState?.posts?.map((post) => ({ + ...post, + onDelete: service.hasModifyPostPermission(user, timeline, post) + ? () => { + service.deletePost(name, post.id).subscribe({ + error: () => { + pushAlert({ + type: "danger", + message: t("timeline.deletePostFailed"), + }); + }, + }); + } + : undefined, + })); + + const others = { + onPost: service.hasPostPermission(user, timeline) + ? onPost + : undefined, + onManage: service.hasManagePermission(user, timeline) + ? onManage + : undefined, + onMember: () => setDialog("member"), + }; + + if (type === "cache") { + return [{ timeline, posts, ...others }, "syncing"]; + } else if (type === "offline") { + return [{ timeline, posts, ...others }, "offline"]; + } else { + if (postListState == null) { + return [{ timeline, posts, ...others }, "syncing"]; + } else { + const { type: postListType } = postListState; + if (postListType === "synced") { + return [{ timeline, posts, ...others }, "synced"]; + } else if (postListType === "cache") { + return [{ timeline, posts, ...others }, "syncing"]; + } else if (postListType === "offline") { + return [{ timeline, posts, ...others }, "offline"]; + } + } + } + } } - return undefined; + throw new UiLogicError("Failed to calculate TimelinePageUITemplate props."); })(); const closeDialog = React.useCallback((): void => { @@ -64,6 +147,8 @@ export default function TimelinePageTemplate( let dialogElement: React.ReactElement | undefined; + const timeline = timelineState?.timeline; + if (dialog === "property") { if (timeline == null) { throw new UiLogicError( @@ -129,57 +214,9 @@ export default function TimelinePageTemplate( const { UiComponent } = props; - const onDelete: TimelineDeleteCallback = React.useCallback( - (index, id) => { - service.deletePost(name, id).subscribe(null, () => { - pushAlert({ - type: "danger", - message: t("timeline.deletePostFailed"), - }); - }); - }, - [service, name, t] - ); - - const onPost: TimelinePostSendCallback = React.useCallback( - (req) => { - return service.createPost(name, req).toPromise().then(); - }, - [service, name] - ); - - const onManageProp = props.onManage; - - const onManage = React.useCallback( - (item: "property" | TManageItem) => { - if (item === "property") { - setDialog(item); - } else { - onManageProp(item); - } - }, - [onManageProp] - ); - return ( <> - setDialog("member")} - /> + {dialogElement} ); diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx index f27171f5..01561704 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx @@ -4,18 +4,10 @@ import { fromEvent } from "rxjs"; import { Spinner } from "react-bootstrap"; import { getAlertHost } from "@/services/alert"; -import { useEventEmiiter, UiLogicError } from "@/common"; -import { - TimelineInfo, - TimelinePostsWithSyncState, - timelineService, -} from "@/services/timeline"; -import { userService } from "@/services/user"; - -import Timeline, { - TimelinePostInfoEx, - TimelineDeleteCallback, -} from "./Timeline"; +import { useEventEmiiter, I18nText, convertI18nText } from "@/common"; +import { TimelineInfo } from "@/services/timeline"; + +import Timeline, { TimelinePostInfoEx } from "./Timeline"; import TimelinePostEdit, { TimelinePostSendCallback } from "./TimelinePostEdit"; import { TimelineSyncStatus } from "./SyncStatusBadge"; @@ -30,20 +22,23 @@ export interface TimelineCardComponentProps { } export interface TimelinePageTemplateUIProps { - timeline?: TimelineInfo; - postListState?: TimelinePostsWithSyncState; + data?: + | { + timeline: TimelineInfo; + posts?: TimelinePostInfoEx[]; + onManage?: (item: TManageItems | "property") => void; + onMember: () => void; + onPost?: TimelinePostSendCallback; + } + | I18nText; + syncStatus: TimelineSyncStatus; CardComponent: React.ComponentType>; - onMember: () => void; - onManage?: (item: TManageItems | "property") => void; - onPost?: TimelinePostSendCallback; - onDelete: TimelineDeleteCallback; - error?: string; } export default function TimelinePageTemplateUI( props: TimelinePageTemplateUIProps ): React.ReactElement | null { - const { timeline, postListState } = props; + const { data, syncStatus, CardComponent } = props; const { t } = useTranslation(); @@ -68,6 +63,9 @@ export default function TimelinePageTemplateUI( const [getResizeEvent, triggerResizeEvent] = useEventEmiiter(); + const timelineName: string | null = + typeof data === "object" && "timeline" in data ? data.timeline.name : null; + React.useEffect(() => { const { current: timelineElement } = timelineRef; if (timelineElement != null) { @@ -115,13 +113,10 @@ export default function TimelinePageTemplateUI( subscriptions.forEach((s) => s.unsubscribe()); }; } - }, [getResizeEvent, triggerResizeEvent, timeline, postListState]); - - const genCardCollapseLocalStorageKey = (uniqueId: string): string => - `timeline.${uniqueId}.cardCollapse`; + }, [getResizeEvent, triggerResizeEvent, timelineName]); const cardCollapseLocalStorageKey = - timeline != null ? genCardCollapseLocalStorageKey(timeline.uniqueId) : null; + timelineName != null ? `timeline.${timelineName}.cardCollapse` : null; const [cardCollapse, setCardCollapse] = React.useState(true); React.useEffect(() => { @@ -135,9 +130,9 @@ export default function TimelinePageTemplateUI( const toggleCardCollapse = (): void => { const newState = !cardCollapse; setCardCollapse(newState); - if (timeline != null) { + if (cardCollapseLocalStorageKey != null) { window.localStorage.setItem( - genCardCollapseLocalStorageKey(timeline.uniqueId), + cardCollapseLocalStorageKey, newState.toString() ); } @@ -145,98 +140,51 @@ export default function TimelinePageTemplateUI( let body: React.ReactElement; - if (props.error != null) { - body =

{t(props.error)}

; + if (data != null && (typeof data === "string" || "type" in data)) { + body =

{convertI18nText(data, t)}

; } else { - if (timeline != null) { - let timelineBody: React.ReactElement; - if (postListState != null) { - if (postListState.type === "notexist") { - throw new UiLogicError( - "Timeline is not null but post list state is notexist." - ); - } - if (postListState.type === "forbid") { - timelineBody = ( -

{t("timeline.messageCantSee")}

- ); - } else { - const posts: TimelinePostInfoEx[] = postListState.posts.map( - (post) => ({ - ...post, - deletable: timelineService.hasModifyPostPermission( - userService.currentUser, - timeline, - post - ), - }) - ); - - timelineBody = ( - - ); - if (props.onPost != null) { - timelineBody = ( - <> - {timelineBody} -
- - - ); - } - } - } else { - timelineBody = ( -
- -
- ); - } + const posts = data?.posts; - const { CardComponent } = props; - const syncStatus: TimelineSyncStatus = - postListState == null || postListState.syncing - ? "syncing" - : postListState.type === "synced" - ? "synced" - : "offline"; - - body = ( - <> -
+ body = ( +
+ {data != null ? ( - {timelineBody} - - ); - } else { - body = ( -
- -
- ); - } + ) : null} + {posts != null ? ( + + ) : ( +
+ +
+ )} + {data != null && data.onPost != null ? ( + <> +
+ + + ) : null} +
+ ); } - return body; } -- cgit v1.2.3