diff options
author | crupest <crupest@outlook.com> | 2021-01-19 16:20:42 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-01-19 16:20:42 +0800 |
commit | a5deeea162433c52a3b07c20b34831522ee01acb (patch) | |
tree | 8c063ec3fa3ed0233a4ac1e5d214c0124d16ff7d /FrontEnd/src/app/views | |
parent | ee419812021f8b2e8e35997750662e56c9db613a (diff) | |
download | timeline-a5deeea162433c52a3b07c20b34831522ee01acb.tar.gz timeline-a5deeea162433c52a3b07c20b34831522ee01acb.tar.bz2 timeline-a5deeea162433c52a3b07c20b34831522ee01acb.zip |
...
Diffstat (limited to 'FrontEnd/src/app/views')
3 files changed, 127 insertions, 107 deletions
diff --git a/FrontEnd/src/app/views/timeline-common/Timeline.tsx b/FrontEnd/src/app/views/timeline-common/Timeline.tsx index ab658b89..2446c0dd 100644 --- a/FrontEnd/src/app/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/app/views/timeline-common/Timeline.tsx @@ -1,11 +1,16 @@ import React from "react"; import clsx from "clsx"; -import { TimelinePostInfo } from "@/services/timeline"; +import { + TimelineInfo, + TimelinePostInfo, + timelineService, +} from "@/services/timeline"; import TimelineItem from "./TimelineItem"; import TimelineTop from "./TimelineTop"; import TimelineDateItem from "./TimelineDateItem"; +import { useUser } from "@/services/user"; function dateEqual(left: Date, right: Date): boolean { return ( @@ -15,30 +20,27 @@ function dateEqual(left: Date, right: Date): boolean { ); } -export interface TimelinePostInfoEx extends TimelinePostInfo { - onDelete?: () => void; -} - -export type TimelineDeleteCallback = (index: number, id: number) => void; - export interface TimelineProps { className?: string; style?: React.CSSProperties; - posts: TimelinePostInfoEx[]; - containerRef?: React.Ref<HTMLDivElement>; + timeline: TimelineInfo; + posts: TimelinePostInfo[]; + onDelete: (post: TimelinePostInfo) => void; } const Timeline: React.FC<TimelineProps> = (props) => { - const { posts } = props; + const { timeline, posts } = props; + + const user = useUser(); const [showMoreIndex, setShowMoreIndex] = React.useState<number>(-1); const groupedPosts = React.useMemo< - { date: Date; posts: (TimelinePostInfoEx & { index: number })[] }[] + { date: Date; posts: (TimelinePostInfo & { index: number })[] }[] >(() => { const result: { date: Date; - posts: (TimelinePostInfoEx & { index: number })[]; + posts: (TimelinePostInfo & { index: number })[]; }[] = []; let index = 0; for (const post of posts) { @@ -59,36 +61,41 @@ const Timeline: React.FC<TimelineProps> = (props) => { }, [posts]); return ( - <div - ref={props.containerRef} - style={props.style} - className={clsx("timeline", props.className)} - > + <div style={props.style} className={clsx("timeline", props.className)}> <TimelineTop height="56px" /> {groupedPosts.map((group) => { return ( <> <TimelineDateItem date={group.date} /> - {group.posts.map((post) => ( - <TimelineItem - post={post} - key={post.id} - current={posts.length - 1 === post.index} - more={ - post.onDelete != null - ? { - isOpen: showMoreIndex === post.index, - toggle: () => - setShowMoreIndex((old) => - old === post.index ? -1 : post.index - ), - onDelete: post.onDelete, - } - : undefined - } - onClick={() => setShowMoreIndex(-1)} - /> - ))} + {group.posts.map((post) => { + const deletable = timelineService.hasModifyPostPermission( + user, + timeline, + post + ); + return ( + <TimelineItem + post={post} + key={post.id} + current={posts.length - 1 === post.index} + more={ + deletable + ? { + isOpen: showMoreIndex === post.index, + toggle: () => + setShowMoreIndex((old) => + old === post.index ? -1 : post.index + ), + onDelete: () => { + props.onDelete(post); + }, + } + : undefined + } + onClick={() => setShowMoreIndex(-1)} + /> + ); + })} </> ); })} diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index fc4c52ec..da020be4 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -4,16 +4,20 @@ import { useTranslation } from "react-i18next"; import { UiLogicError } from "@/common"; import { pushAlert } from "@/services/alert"; import { useUser } from "@/services/user"; -import { timelineService, usePosts, useTimeline } from "@/services/timeline"; +import { + TimelinePostInfo, + timelineService, + usePosts, + useTimeline, +} from "@/services/timeline"; import { mergeDataStatus } from "@/services/DataHub2"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; import { - TimelinePageTemplateData, + TimelinePageTemplateUIOperations, TimelinePageTemplateUIProps, } from "./TimelinePageTemplateUI"; -import { TimelinePostInfoEx } from "./Timeline"; export interface TimelinePageTemplateProps<TManageItem> { name: string; @@ -71,41 +75,26 @@ export default function TimelinePageTemplate<TManageItem>( }; }, [name, scrollToBottomNextSyncKey]); - const data = ((): TimelinePageTemplateUIProps<TManageItem>["data"] => { + const uiTimelineProp = ((): TimelinePageTemplateUIProps<TManageItem>["timeline"] => { const { status, data: timeline } = timelineAndStatus; if (timeline == null) { if (status === "offline") { - return { type: "custom", value: "Network Error" }; + return "offline"; } else { return undefined; } } else if (timeline === "notexist") { - return props.notFoundI18nKey; + return "notexist"; } else { - const posts = ((): TimelinePostInfoEx[] | "forbid" | undefined => { - const { data: postsInfo } = postsAndState; - if (postsInfo === "forbid") { - return "forbid"; - } else if (postsInfo == null || postsInfo === "notexist") { - return undefined; - } else { - return postsInfo.posts.map((post) => ({ - ...post, - onDelete: service.hasModifyPostPermission(user, timeline, post) - ? () => { - service.deletePost(name, post.id).catch(() => { - pushAlert({ - type: "danger", - message: t("timeline.deletePostFailed"), - }); - }); - } - : undefined, - })); - } - })(); - - const operations: TimelinePageTemplateData<TManageItem>["operations"] = { + const operations: TimelinePageTemplateUIOperations<TManageItem> = { + onDeletePost: (post) => { + service.deletePost(name, post.id).catch(() => { + pushAlert({ + type: "danger", + message: t("timeline.deletePostFailed"), + }); + }); + }, onPost: service.hasPostPermission(user, timeline) ? (req) => service.createPost(name, req).then(() => scrollToBottomNextSync()) @@ -158,7 +147,18 @@ export default function TimelinePageTemplate<TManageItem>( : undefined, }; - return { timeline, posts, operations }; + const posts = ((): TimelinePostInfo[] | "forbid" | undefined => { + const { data: postsInfo } = postsAndState; + if (postsInfo === "forbid") { + return "forbid"; + } else if (postsInfo == null || postsInfo === "notexist") { + return undefined; + } else { + return postsInfo.posts; + } + })(); + + return { ...timeline, operations, posts }; } })(); @@ -203,11 +203,12 @@ export default function TimelinePageTemplate<TManageItem>( return ( <> <UiComponent - data={data} + timeline={uiTimelineProp} syncStatus={mergeDataStatus([ timelineAndStatus.status, postsAndState.status, ])} + notExistMessageI18nKey={props.notFoundI18nKey} /> {dialogElement} </> diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx index 0d0951ee..815906d3 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx @@ -3,10 +3,9 @@ import { useTranslation } from "react-i18next"; import { Spinner } from "react-bootstrap"; import { getAlertHost } from "@/services/alert"; -import { I18nText, convertI18nText } from "@/common"; -import { TimelineInfo } from "@/services/timeline"; +import { TimelineInfo, TimelinePostInfo } from "@/services/timeline"; -import Timeline, { TimelinePostInfoEx } from "./Timeline"; +import Timeline from "./Timeline"; import TimelinePostEdit, { TimelinePostSendCallback } from "./TimelinePostEdit"; import { TimelineSyncStatus } from "./SyncStatusBadge"; @@ -24,28 +23,32 @@ export interface TimelineCardComponentProps<TManageItems> { className?: string; } -export interface TimelinePageTemplateData<TManageItems> { - timeline: TimelineInfo; - posts?: TimelinePostInfoEx[] | "forbid"; - operations: { - onManage?: (item: TManageItems | "property") => void; - onMember: () => void; - onBookmark?: () => void; - onHighlight?: () => void; - onPost?: TimelinePostSendCallback; - }; +export interface TimelinePageTemplateUIOperations<TManageItems> { + onDeletePost: (post: TimelinePostInfo) => void; + onManage?: (item: TManageItems | "property") => void; + onMember: () => void; + onBookmark?: () => void; + onHighlight?: () => void; + onPost?: TimelinePostSendCallback; } export interface TimelinePageTemplateUIProps<TManageItems> { - data?: TimelinePageTemplateData<TManageItems> | I18nText; + timeline?: + | (TimelineInfo & { + operations: TimelinePageTemplateUIOperations<TManageItems>; + posts?: TimelinePostInfo[] | "forbid"; + }) + | "notexist" + | "offline"; syncStatus: TimelineSyncStatus; + notExistMessageI18nKey: string; CardComponent: React.ComponentType<TimelineCardComponentProps<TManageItems>>; } export default function TimelinePageTemplateUI<TManageItems>( props: TimelinePageTemplateUIProps<TManageItems> ): React.ReactElement | null { - const { data, syncStatus, CardComponent } = props; + const { timeline, syncStatus, CardComponent } = props; const { t } = useTranslation(); @@ -66,10 +69,7 @@ export default function TimelinePageTemplateUI<TManageItems>( } }, []); - const timelineRef = React.useRef<HTMLDivElement | null>(null); - - const timelineName: string | null = - typeof data === "object" && "timeline" in data ? data.timeline.name : null; + const timelineName = typeof timeline === "object" ? timeline.name : null; const cardCollapseLocalStorageKey = timelineName != null ? `timeline.${timelineName}.cardCollapse` : null; @@ -96,32 +96,44 @@ export default function TimelinePageTemplateUI<TManageItems>( let body: React.ReactElement; - if (data != null && (typeof data === "string" || "type" in data)) { - body = <p className="text-danger">{convertI18nText(data, t)}</p>; + if (timeline == null) { + body = ( + <div className="full-viewport-center-child"> + <Spinner variant="primary" animation="grow" /> + </div> + ); + } else if (timeline === "offline") { + // TODO: i18n + body = <p className="text-danger">Offline!</p>; + } else if (timeline === "notexist") { + body = <p className="text-danger">{t(props.notExistMessageI18nKey)}</p>; } else { - const posts = data?.posts; - + const { operations, posts } = timeline; body = ( <> - {data != null ? ( - <CardComponent - className="timeline-template-card" - timeline={data.timeline} - operations={data.operations} - syncStatus={syncStatus} - collapse={cardCollapse} - toggleCollapse={toggleCardCollapse} - /> - ) : null} + <CardComponent + className="timeline-template-card" + timeline={timeline} + operations={operations} + syncStatus={syncStatus} + collapse={cardCollapse} + toggleCollapse={toggleCardCollapse} + /> {posts != null ? ( posts === "forbid" ? ( <div>{t("timeline.messageCantSee")}</div> ) : ( <div className="timeline-container" - style={{ minHeight: `calc(100vh - ${56 + bottomSpaceHeight}px)` }} + style={{ + minHeight: `calc(100vh - ${56 + bottomSpaceHeight}px)`, + }} > - <Timeline containerRef={timelineRef} posts={posts} /> + <Timeline + timeline={timeline} + posts={posts} + onDelete={operations.onDeletePost} + /> </div> ) ) : ( @@ -129,7 +141,7 @@ export default function TimelinePageTemplateUI<TManageItems>( <Spinner variant="primary" animation="grow" /> </div> )} - {data != null && data.operations.onPost != null ? ( + {operations.onPost != null ? ( <> <div style={{ height: bottomSpaceHeight }} @@ -137,9 +149,9 @@ export default function TimelinePageTemplateUI<TManageItems>( /> <TimelinePostEdit className="fixed-bottom" - onPost={data.operations.onPost} + onPost={operations.onPost} onHeightChange={onPostEditHeightChange} - timelineUniqueId={data.timeline.uniqueId} + timelineUniqueId={timeline.uniqueId} /> </> ) : null} |