aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-11-11 21:02:15 +0800
committercrupest <crupest@outlook.com>2020-11-11 21:02:15 +0800
commiteb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb (patch)
treef36a8e62cf17323ab6d1c8f9e1be598c55f39f00 /FrontEnd
parentd3a9a1ca377dbc9d5ff641470407d30924bb5e34 (diff)
downloadtimeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.tar.gz
timeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.tar.bz2
timeline-eb75a01f00ff9ba34ef95b9d96e1c4141b3f08fb.zip
refactor: Refactor timeline props.
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/src/app/views/timeline-common/Timeline.tsx9
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx153
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx170
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;
}