diff options
author | crupest <crupest@outlook.com> | 2020-11-11 21:02:15 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-11-11 21:02:15 +0800 |
commit | eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb (patch) | |
tree | f36a8e62cf17323ab6d1c8f9e1be598c55f39f00 /FrontEnd | |
parent | d3a9a1ca377dbc9d5ff641470407d30924bb5e34 (diff) | |
download | timeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.tar.gz timeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.tar.bz2 timeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.zip |
refactor: Refactor timeline props.
Diffstat (limited to 'FrontEnd')
3 files changed, 158 insertions, 174 deletions
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<HTMLDivElement>; } const Timeline: React.FC<TimelineProps> = (props) => { - const { posts, onDelete, onResize } = props; + const { posts, onResize } = props; const [showMoreIndex, setShowMoreIndex] = React.useState<number>(-1); @@ -42,12 +41,12 @@ const Timeline: React.FC<TimelineProps> = (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<TManageItem> { name: string; @@ -43,19 +44,101 @@ export default function TimelinePageTemplate<TManageItem>( ); 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<TManageItem>["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<TManageItem>( 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<TManageItem>( 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 ( <> - <UiComponent - error={error} - timeline={timeline ?? undefined} - postListState={postListState} - onDelete={onDelete} - onPost={ - timeline != null && service.hasPostPermission(user, timeline) - ? onPost - : undefined - } - onManage={ - timeline != null && service.hasManagePermission(user, timeline) - ? onManage - : undefined - } - onMember={() => setDialog("member")} - /> + <UiComponent data={childProps[0]} syncStatus={childProps[1]} /> {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<TManageItems> { } export interface TimelinePageTemplateUIProps<TManageItems> { - timeline?: TimelineInfo; - postListState?: TimelinePostsWithSyncState; + data?: + | { + timeline: TimelineInfo; + posts?: TimelinePostInfoEx[]; + onManage?: (item: TManageItems | "property") => void; + onMember: () => void; + onPost?: TimelinePostSendCallback; + } + | I18nText; + syncStatus: TimelineSyncStatus; CardComponent: React.ComponentType<TimelineCardComponentProps<TManageItems>>; - onMember: () => void; - onManage?: (item: TManageItems | "property") => void; - onPost?: TimelinePostSendCallback; - onDelete: TimelineDeleteCallback; - error?: string; } export default function TimelinePageTemplateUI<TManageItems>( props: TimelinePageTemplateUIProps<TManageItems> ): React.ReactElement | null { - const { timeline, postListState } = props; + const { data, syncStatus, CardComponent } = props; const { t } = useTranslation(); @@ -68,6 +63,9 @@ export default function TimelinePageTemplateUI<TManageItems>( 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<TManageItems>( 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<boolean>(true); React.useEffect(() => { @@ -135,9 +130,9 @@ export default function TimelinePageTemplateUI<TManageItems>( 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<TManageItems>( let body: React.ReactElement; - if (props.error != null) { - body = <p className="text-danger">{t(props.error)}</p>; + if (data != null && (typeof data === "string" || "type" in data)) { + body = <p className="text-danger">{convertI18nText(data, t)}</p>; } 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 = ( - <p className="text-danger">{t("timeline.messageCantSee")}</p> - ); - } else { - const posts: TimelinePostInfoEx[] = postListState.posts.map( - (post) => ({ - ...post, - deletable: timelineService.hasModifyPostPermission( - userService.currentUser, - timeline, - post - ), - }) - ); - - timelineBody = ( - <Timeline - containerRef={timelineRef} - posts={posts} - onDelete={props.onDelete} - onResize={triggerResizeEvent} - /> - ); - if (props.onPost != null) { - timelineBody = ( - <> - {timelineBody} - <div - style={{ height: bottomSpaceHeight }} - className="flex-fix-length" - /> - <TimelinePostEdit - className="fixed-bottom" - onPost={props.onPost} - onHeightChange={onPostEditHeightChange} - timelineUniqueId={timeline.uniqueId} - /> - </> - ); - } - } - } else { - timelineBody = ( - <div className="full-viewport-center-child"> - <Spinner variant="primary" animation="grow" /> - </div> - ); - } + const posts = data?.posts; - const { CardComponent } = props; - const syncStatus: TimelineSyncStatus = - postListState == null || postListState.syncing - ? "syncing" - : postListState.type === "synced" - ? "synced" - : "offline"; - - body = ( - <> - <div className="timeline-background" /> + body = ( + <div className="timeline-background"> + {data != null ? ( <CardComponent className="timeline-template-card" - timeline={timeline} - onManage={props.onManage} - onMember={props.onMember} + timeline={data.timeline} + onManage={data.onManage} + onMember={data.onMember} syncStatus={syncStatus} collapse={cardCollapse} toggleCollapse={toggleCardCollapse} /> - {timelineBody} - </> - ); - } else { - body = ( - <div className="full-viewport-center-child"> - <Spinner variant="primary" animation="grow" /> - </div> - ); - } + ) : null} + {posts != null ? ( + <Timeline + containerRef={timelineRef} + posts={posts} + onResize={triggerResizeEvent} + /> + ) : ( + <div className="full-viewport-center-child"> + <Spinner variant="primary" animation="grow" /> + </div> + )} + {data != null && data.onPost != null ? ( + <> + <div + style={{ height: bottomSpaceHeight }} + className="flex-fix-length" + /> + <TimelinePostEdit + className="fixed-bottom" + onPost={data.onPost} + onHeightChange={onPostEditHeightChange} + timelineUniqueId={data.timeline.uniqueId} + /> + </> + ) : null} + </div> + ); } - return body; } |