aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-01-19 16:20:42 +0800
committercrupest <crupest@outlook.com>2021-01-19 16:20:42 +0800
commita5deeea162433c52a3b07c20b34831522ee01acb (patch)
tree8c063ec3fa3ed0233a4ac1e5d214c0124d16ff7d /FrontEnd/src/app/views
parentee419812021f8b2e8e35997750662e56c9db613a (diff)
downloadtimeline-a5deeea162433c52a3b07c20b34831522ee01acb.tar.gz
timeline-a5deeea162433c52a3b07c20b34831522ee01acb.tar.bz2
timeline-a5deeea162433c52a3b07c20b34831522ee01acb.zip
...
Diffstat (limited to 'FrontEnd/src/app/views')
-rw-r--r--FrontEnd/src/app/views/timeline-common/Timeline.tsx81
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx65
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx88
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}