From 58e23e759d730dd9d9733a64e5f16cc5aafeba35 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 15 Feb 2021 01:08:05 +0800 Subject: refactor: Refactor timeline card. --- .../views/timeline-common/TimelineCardTemplate.tsx | 137 ------------- .../timeline-common/TimelinePageCardTemplate.tsx | 173 +++++++++++++++++ .../views/timeline-common/TimelinePageTemplate.tsx | 213 +++++++++++++-------- .../timeline-common/TimelinePageTemplateUI.tsx | 167 ---------------- FrontEnd/src/app/views/timeline/TimelineCard.tsx | 85 ++++++++ .../app/views/timeline/TimelineDeleteDialog.tsx | 10 +- .../src/app/views/timeline/TimelineInfoCard.tsx | 70 ------- FrontEnd/src/app/views/timeline/TimelinePageUI.tsx | 20 -- FrontEnd/src/app/views/timeline/index.tsx | 33 +--- FrontEnd/src/app/views/user/UserCard.tsx | 107 +++++++++++ FrontEnd/src/app/views/user/UserInfoCard.tsx | 70 ------- FrontEnd/src/app/views/user/UserPageUI.tsx | 18 -- FrontEnd/src/app/views/user/index.tsx | 44 +---- 13 files changed, 516 insertions(+), 631 deletions(-) delete mode 100644 FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx create mode 100644 FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx delete mode 100644 FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx create mode 100644 FrontEnd/src/app/views/timeline/TimelineCard.tsx delete mode 100644 FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx delete mode 100644 FrontEnd/src/app/views/timeline/TimelinePageUI.tsx create mode 100644 FrontEnd/src/app/views/user/UserCard.tsx delete mode 100644 FrontEnd/src/app/views/user/UserInfoCard.tsx delete mode 100644 FrontEnd/src/app/views/user/UserPageUI.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx deleted file mode 100644 index c29e628d..00000000 --- a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import { useTranslation } from "react-i18next"; -import { Dropdown, Button } from "react-bootstrap"; - -import { getHttpHighlightClient } from "@/http/highlight"; -import { getHttpBookmarkClient } from "@/http/bookmark"; - -import { useUser } from "@/services/user"; -import { pushAlert } from "@/services/alert"; -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; - -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import CollapseButton from "../timeline-common/CollapseButton"; - -export interface TimelineCardTemplateProps - extends Omit, "operations"> { - infoArea: React.ReactElement; - manageArea: - | { type: "member"; onMember: () => void } - | { - type: "manage"; - items: ( - | { - type: "button"; - text: string; - color?: string; - onClick: () => void; - } - | { type: "divider" } - )[]; - }; -} - -function TimelineCardTemplate({ - timeline, - collapse, - infoArea, - manageArea, - toggleCollapse, - className, -}: TimelineCardTemplateProps): React.ReactElement | null { - const { t } = useTranslation(); - - const user = useUser(); - - return ( -
-
- -
-
- {infoArea} -

{timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - -
- { - getHttpHighlightClient() - [timeline.isHighlight ? "delete" : "put"](timeline.name) - .catch(() => { - pushAlert({ - message: timeline.isHighlight - ? "timeline.removeHighlightFail" - : "timeline.addHighlightFail", - type: "danger", - }); - }); - } - : undefined - } - /> - {user != null ? ( - { - getHttpBookmarkClient() - [timeline.isBookmark ? "delete" : "put"](timeline.name) - .catch(() => { - pushAlert({ - message: timeline.isBookmark - ? "timeline.removeBookmarkFail" - : "timeline.addBookmarkFail", - type: "danger", - }); - }); - }} - /> - ) : null} - {manageArea.type === "manage" ? ( - - - {t("timeline.manage")} - - - {manageArea.items.map((item, index) => { - if (item.type === "divider") { - return ; - } else { - return ( - - {t(item.text)} - - ); - } - })} - - - ) : ( - - )} -
-
-
- ); -} - -export default TimelineCardTemplate; diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx new file mode 100644 index 00000000..921f1390 --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageCardTemplate.tsx @@ -0,0 +1,173 @@ +import React from "react"; +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; +import { Dropdown, Button } from "react-bootstrap"; + +import { getHttpHighlightClient } from "@/http/highlight"; +import { getHttpBookmarkClient } from "@/http/bookmark"; + +import { useUser } from "@/services/user"; +import { pushAlert } from "@/services/alert"; +import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; + +import { TimelinePageCardProps } from "./TimelinePageTemplate"; + +import CollapseButton from "./CollapseButton"; +import { TimelineMemberDialog } from "./TimelineMember"; +import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; + +export interface TimelineCardTemplateProps extends TimelinePageCardProps { + infoArea: React.ReactElement; + manageArea: + | { type: "member" } + | { + type: "manage"; + items: ( + | { + type: "button"; + text: string; + color?: string; + onClick: () => void; + } + | { type: "divider" } + )[]; + }; + dialog: string | "property" | "member" | null; + setDialog: (dialog: "property" | "member" | null) => void; +} + +const TimelinePageCardTemplate: React.FC = ({ + timeline, + collapse, + toggleCollapse, + infoArea, + manageArea, + onReload, + className, + dialog, + setDialog, +}) => { + const { t } = useTranslation(); + + const user = useUser(); + + return ( + <> +
+
+ +
+
+ {infoArea} +

{timeline.description}

+ + {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} + +
+ { + getHttpHighlightClient() + [timeline.isHighlight ? "delete" : "put"](timeline.name) + .catch(() => { + pushAlert({ + message: timeline.isHighlight + ? "timeline.removeHighlightFail" + : "timeline.addHighlightFail", + type: "danger", + }); + }); + } + : undefined + } + /> + {user != null ? ( + { + getHttpBookmarkClient() + [timeline.isBookmark ? "delete" : "put"](timeline.name) + .catch(() => { + pushAlert({ + message: timeline.isBookmark + ? "timeline.removeBookmarkFail" + : "timeline.addBookmarkFail", + type: "danger", + }); + }); + }} + /> + ) : null} + {manageArea.type === "manage" ? ( + + + {t("timeline.manage")} + + + {manageArea.items.map((item, index) => { + if (item.type === "divider") { + return ; + } else { + return ( + + {t(item.text)} + + ); + } + })} + + + ) : ( + + )} +
+
+
+ {(() => { + if (dialog === "member") { + return ( + setDialog(null)} + open + onChange={onReload} + /> + ); + } else if (dialog === "property") { + return ( + setDialog(null)} + open + onChange={onReload} + /> + ); + } + })()} + + ); +}; + +export default TimelinePageCardTemplate; diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 92eb0887..3087c20e 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -1,33 +1,39 @@ import React from "react"; - -import { UiLogicError } from "@/common"; +import { useTranslation } from "react-i18next"; +import { Spinner } from "react-bootstrap"; import { HttpNetworkError, HttpNotFoundError } from "@/http/common"; -import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; - -import { TimelineMemberDialog } from "./TimelineMember"; -import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; -import { TimelinePageTemplateUIProps } from "./TimelinePageTemplateUI"; - -export interface TimelinePageTemplateProps { - name: string; - onManage: (item: TManageItem) => void; - UiComponent: React.ComponentType< - Omit, "CardComponent"> - >; +import { + getHttpTimelineClient, + HttpTimelineInfo, + HttpTimelinePostInfo, +} from "@/http/timeline"; + +import { getAlertHost } from "@/services/alert"; + +import Timeline from "./Timeline"; +import TimelinePostEdit from "./TimelinePostEdit"; + +export interface TimelinePageCardProps { + timeline: HttpTimelineInfo; + collapse: boolean; + toggleCollapse: () => void; + className?: string; + onReload: () => void; +} + +export interface TimelinePageTemplateProps { + timelineName: string; notFoundI18nKey: string; reloadKey: number; onReload: () => void; + CardComponent: React.ComponentType; } -export default function TimelinePageTemplate( - props: TimelinePageTemplateProps -): React.ReactElement | null { - const { name, reloadKey, onReload } = props; +const TimelinePageTemplate: React.FC = (props) => { + const { timelineName, reloadKey, onReload, CardComponent } = props; - const [dialog, setDialog] = React.useState( - null - ); + const { t } = useTranslation(); const [timeline, setTimeline] = React.useState< HttpTimelineInfo | "loading" | "offline" | "notexist" | "error" @@ -38,7 +44,7 @@ export default function TimelinePageTemplate( let subscribe = true; void getHttpTimelineClient() - .getTimeline(name) + .getTimeline(timelineName) .then( (data) => { if (subscribe) { @@ -61,70 +67,117 @@ export default function TimelinePageTemplate( return () => { subscribe = false; }; - }, [name, reloadKey]); + }, [timelineName, reloadKey]); - let dialogElement: React.ReactElement | undefined; - const closeDialog = (): void => setDialog(null); + const scrollToBottom = React.useCallback(() => { + window.scrollTo(0, document.body.scrollHeight); + }, []); - if (dialog === "property") { - if (typeof timeline !== "object") { - throw new UiLogicError( - "Timeline is null but attempt to open change property dialog." - ); + const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState(0); + + const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); + + const [newPosts, setNewPosts] = React.useState([]); + + const reloadTimeline = (): void => { + setTimelineReloadKey((old) => old + 1); + setNewPosts([]); + }; + + const onPostEditHeightChange = React.useCallback((height: number): void => { + setBottomSpaceHeight(height); + if (height === 0) { + const alertHost = getAlertHost(); + if (alertHost != null) { + alertHost.style.removeProperty("margin-bottom"); + } + } else { + const alertHost = getAlertHost(); + if (alertHost != null) { + alertHost.style.marginBottom = `${height}px`; + } } + }, []); + + const cardCollapseLocalStorageKey = `timeline.${timelineName}.cardCollapse`; - dialogElement = ( - + const [cardCollapse, setCardCollapse] = React.useState(true); + React.useEffect(() => { + const savedCollapse = + window.localStorage.getItem(cardCollapseLocalStorageKey) === "true"; + setCardCollapse(savedCollapse); + }, [cardCollapseLocalStorageKey]); + + const toggleCardCollapse = (): void => { + const newState = !cardCollapse; + setCardCollapse(newState); + window.localStorage.setItem( + cardCollapseLocalStorageKey, + newState.toString() ); - } else if (dialog === "member") { - if (typeof timeline !== "object") { - throw new UiLogicError( - "Timeline is null but attempt to open change property dialog." - ); - } + }; + + let body: React.ReactElement; - dialogElement = ( - + if (timeline == "loading") { + body = ( +
+ +
+ ); + } else if (timeline === "offline") { + // TODO: i18n + body =

Offline!

; + } else if (timeline === "notexist") { + body =

{t(props.notFoundI18nKey)}

; + } else if (timeline === "error") { + // TODO: i18n + body =

Error!

; + } else { + body = ( + <> + +
+ +
+ {timeline.postable ? ( + <> +
+ { + setNewPosts((old) => [...old, newPost]); + }} + /> + + ) : null} + ); } + return body; +}; - const { UiComponent } = props; - - return ( - <> - { - if (item === "property") { - setDialog(item); - } else { - props.onManage(item); - } - } - : undefined, - onMember: () => setDialog("member"), - }, - } - : timeline - } - notExistMessageI18nKey={props.notFoundI18nKey} - /> - {dialogElement} - - ); -} +export default TimelinePageTemplate; diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx deleted file mode 100644 index 7319d84d..00000000 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { Spinner } from "react-bootstrap"; - -import { getAlertHost } from "@/services/alert"; - -import { HttpTimelineInfo, HttpTimelinePostInfo } from "@/http/timeline"; - -import Timeline from "./Timeline"; -import TimelinePostEdit from "./TimelinePostEdit"; - -export interface TimelineCardComponentProps { - timeline: HttpTimelineInfo; - operations: { - onManage?: (item: TManageItems | "property") => void; - onMember: () => void; - }; - collapse: boolean; - toggleCollapse: () => void; - className?: string; -} - -export interface TimelinePageTemplateUIOperations { - onManage?: (item: TManageItems | "property") => void; - onMember: () => void; - onBookmark?: () => void; - onHighlight?: () => void; -} - -export interface TimelinePageTemplateUIProps { - timeline: - | (HttpTimelineInfo & { - operations: TimelinePageTemplateUIOperations; - }) - | "notexist" - | "offline" - | "loading" - | "error"; - notExistMessageI18nKey: string; - CardComponent: React.ComponentType>; -} - -export default function TimelinePageTemplateUI( - props: TimelinePageTemplateUIProps -): React.ReactElement | null { - const { timeline, CardComponent } = props; - - const { t } = useTranslation(); - - const scrollToBottom = React.useCallback(() => { - window.scrollTo(0, document.body.scrollHeight); - }, []); - - const [bottomSpaceHeight, setBottomSpaceHeight] = React.useState(0); - - const [timelineReloadKey, setTimelineReloadKey] = React.useState(0); - - const [newPosts, setNewPosts] = React.useState([]); - - const reloadTimeline = (): void => { - setTimelineReloadKey((old) => old + 1); - setNewPosts([]); - }; - - const onPostEditHeightChange = React.useCallback((height: number): void => { - setBottomSpaceHeight(height); - if (height === 0) { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.removeProperty("margin-bottom"); - } - } else { - const alertHost = getAlertHost(); - if (alertHost != null) { - alertHost.style.marginBottom = `${height}px`; - } - } - }, []); - - const timelineName = typeof timeline === "object" ? timeline.name : null; - - const cardCollapseLocalStorageKey = - timelineName != null ? `timeline.${timelineName}.cardCollapse` : null; - - const [cardCollapse, setCardCollapse] = React.useState(true); - React.useEffect(() => { - if (cardCollapseLocalStorageKey != null) { - const savedCollapse = - window.localStorage.getItem(cardCollapseLocalStorageKey) === "true"; - setCardCollapse(savedCollapse); - } - }, [cardCollapseLocalStorageKey]); - - const toggleCardCollapse = (): void => { - const newState = !cardCollapse; - setCardCollapse(newState); - if (cardCollapseLocalStorageKey != null) { - window.localStorage.setItem( - cardCollapseLocalStorageKey, - newState.toString() - ); - } - }; - - let body: React.ReactElement; - - if (timeline == "loading") { - body = ( -
- -
- ); - } else if (timeline === "offline") { - // TODO: i18n - body =

Offline!

; - } else if (timeline === "notexist") { - body =

{t(props.notExistMessageI18nKey)}

; - } else if (timeline === "error") { - // TODO: i18n - body =

Error!

; - } else { - const { operations } = timeline; - body = ( - <> - -
- -
- {timeline.postable ? ( - <> -
- { - setNewPosts((old) => [...old, newPost]); - }} - /> - - ) : null} - - ); - } - return body; -} diff --git a/FrontEnd/src/app/views/timeline/TimelineCard.tsx b/FrontEnd/src/app/views/timeline/TimelineCard.tsx new file mode 100644 index 00000000..a777cbbd --- /dev/null +++ b/FrontEnd/src/app/views/timeline/TimelineCard.tsx @@ -0,0 +1,85 @@ +import React from "react"; + +import TimelinePageCardTemplate, { + TimelineCardTemplateProps, +} from "../timeline-common/TimelinePageCardTemplate"; +import { TimelinePageCardProps } from "../timeline-common/TimelinePageTemplate"; +import UserAvatar from "../common/user/UserAvatar"; +import TimelineDeleteDialog from "./TimelineDeleteDialog"; + +const TimelineCard: React.FC = (props) => { + const { timeline } = props; + + const [dialog, setDialog] = React.useState< + "member" | "property" | "delete" | null + >(null); + + return ( + <> + +

+ {timeline.title} + {timeline.name} +

+
+ + {timeline.owner.nickname} + + @{timeline.owner.username} + +
+ + } + manageArea={((): TimelineCardTemplateProps["manageArea"] => { + if (!timeline.manageable) { + return { type: "member" }; + } else { + return { + type: "manage", + items: [ + { + type: "button", + text: "timeline.manageItem.property", + onClick: () => setDialog("property"), + }, + { + type: "button", + onClick: () => setDialog("member"), + text: "timeline.manageItem.member", + }, + { type: "divider" }, + { + type: "button", + onClick: () => setDialog("delete"), + color: "danger", + text: "timeline.manageItem.delete", + }, + ], + }; + } + })()} + dialog={dialog} + setDialog={setDialog} + {...props} + /> + {(() => { + if (dialog === "delete") { + return ( + setDialog(null)} + /> + ); + } + })()} + + ); +}; + +export default TimelineCard; diff --git a/FrontEnd/src/app/views/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/app/views/timeline/TimelineDeleteDialog.tsx index f472c16a..dbca62ca 100644 --- a/FrontEnd/src/app/views/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/app/views/timeline/TimelineDeleteDialog.tsx @@ -2,20 +2,20 @@ import React from "react"; import { useHistory } from "react-router"; import { Trans } from "react-i18next"; -import { getHttpTimelineClient } from "@/http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; import OperationDialog from "../common/OperationDialog"; interface TimelineDeleteDialog { + timeline: HttpTimelineInfo; open: boolean; - name: string; close: () => void; } const TimelineDeleteDialog: React.FC = (props) => { const history = useHistory(); - const { name } = props; + const { timeline } = props; return ( = (props) => { }, ]} inputValidator={([value]) => { - if (value !== name) { + if (value !== timeline.name) { return { 0: "timeline.deleteDialog.notMatch" }; } else { return null; } }} onProcess={() => { - return getHttpTimelineClient().deleteTimeline(name); + return getHttpTimelineClient().deleteTimeline(timeline.name); }} onSuccessAndClose={() => { history.replace("/"); diff --git a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx deleted file mode 100644 index 63da6f3c..00000000 --- a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; - -import TimelineCardTemplate, { - TimelineCardTemplateProps, -} from "../timeline-common/TimelineCardTemplate"; -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import UserAvatar from "../common/user/UserAvatar"; - -export type OrdinaryTimelineManageItem = "delete"; - -export type TimelineInfoCardProps = TimelineCardComponentProps; - -const TimelineInfoCard: React.FC = (props) => { - const { timeline, operations } = props; - const { onManage, onMember } = operations; - - return ( - -

- {timeline.title} - {timeline.name} -

-
- - {timeline.owner.nickname} - - @{timeline.owner.username} - -
- - } - manageArea={((): TimelineCardTemplateProps["manageArea"] => { - if (onManage == null) { - return { type: "member", onMember }; - } else { - return { - type: "manage", - items: [ - { - type: "button", - text: "timeline.manageItem.property", - onClick: () => onManage("property"), - }, - { - type: "button", - onClick: onMember, - text: "timeline.manageItem.member", - }, - { type: "divider" }, - { - type: "button", - onClick: () => onManage("delete"), - color: "danger", - text: "timeline.manageItem.delete", - }, - ], - }; - } - })()} - {...props} - /> - ); -}; - -export default TimelineInfoCard; diff --git a/FrontEnd/src/app/views/timeline/TimelinePageUI.tsx b/FrontEnd/src/app/views/timeline/TimelinePageUI.tsx deleted file mode 100644 index 67ea699e..00000000 --- a/FrontEnd/src/app/views/timeline/TimelinePageUI.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; - -import TimelinePageTemplateUI, { - TimelinePageTemplateUIProps, -} from "../timeline-common/TimelinePageTemplateUI"; - -import TimelineInfoCard, { - OrdinaryTimelineManageItem, -} from "./TimelineInfoCard"; - -export type TimelinePageUIProps = Omit< - TimelinePageTemplateUIProps, - "CardComponent" ->; - -const TimelinePageUI: React.FC = (props) => { - return ; -}; - -export default TimelinePageUI; diff --git a/FrontEnd/src/app/views/timeline/index.tsx b/FrontEnd/src/app/views/timeline/index.tsx index 8048dd12..c5bfd7ab 100644 --- a/FrontEnd/src/app/views/timeline/index.tsx +++ b/FrontEnd/src/app/views/timeline/index.tsx @@ -2,38 +2,21 @@ import React from "react"; import { useParams } from "react-router"; import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate"; - -import TimelinePageUI from "./TimelinePageUI"; -import { OrdinaryTimelineManageItem } from "./TimelineInfoCard"; -import TimelineDeleteDialog from "./TimelineDeleteDialog"; +import TimelineCard from "./TimelineCard"; const TimelinePage: React.FC = () => { const { name } = useParams<{ name: string }>(); - const [dialog, setDialog] = React.useState( - null - ); const [reloadKey, setReloadKey] = React.useState(0); - let dialogElement: React.ReactElement | undefined; - if (dialog === "delete") { - dialogElement = ( - setDialog(null)} name={name} /> - ); - } - return ( - <> - setDialog(item)} - notFoundI18nKey="timeline.timelineNotExist" - reloadKey={reloadKey} - onReload={() => setReloadKey(reloadKey + 1)} - /> - {dialogElement} - + setReloadKey(reloadKey + 1)} + /> ); }; diff --git a/FrontEnd/src/app/views/user/UserCard.tsx b/FrontEnd/src/app/views/user/UserCard.tsx new file mode 100644 index 00000000..575ca2c1 --- /dev/null +++ b/FrontEnd/src/app/views/user/UserCard.tsx @@ -0,0 +1,107 @@ +import React from "react"; + +import TimelinePageCardTemplate, { + TimelineCardTemplateProps, +} from "../timeline-common/TimelinePageCardTemplate"; +import { TimelinePageCardProps } from "../timeline-common/TimelinePageTemplate"; +import UserAvatar from "../common/user/UserAvatar"; +import ChangeNicknameDialog from "./ChangeNicknameDialog"; +import { getHttpUserClient } from "@/http/user"; +import ChangeAvatarDialog from "./ChangeAvatarDialog"; + +const UserCard: React.FC = (props) => { + const { timeline, onReload } = props; + + const [dialog, setDialog] = React.useState< + "member" | "property" | "avatar" | "nickname" | null + >(null); + + return ( + <> + +

+ {timeline.title} + {timeline.name} +

+
+ + {timeline.owner.nickname} +
+ + } + manageArea={((): TimelineCardTemplateProps["manageArea"] => { + if (!timeline.manageable) { + return { type: "member" }; + } else { + return { + type: "manage", + items: [ + { + type: "button", + text: "timeline.manageItem.nickname", + onClick: () => setDialog("nickname"), + }, + { + type: "button", + text: "timeline.manageItem.avatar", + onClick: () => setDialog("avatar"), + }, + { + type: "button", + text: "timeline.manageItem.property", + onClick: () => setDialog("property"), + }, + { + type: "button", + text: "timeline.manageItem.member", + onClick: () => setDialog("member"), + }, + ], + }; + } + })()} + dialog={dialog} + setDialog={setDialog} + {...props} + /> + {(() => { + // TODO: Move this two to settings. + if (dialog === "nickname") { + return ( + setDialog(null)} + onProcess={async (newNickname) => { + await getHttpUserClient().patch(timeline.owner.username, { + nickname: newNickname, + }); + onReload(); + }} + /> + ); + } else if (dialog === "avatar") { + return ( + setDialog(null)} + process={async (file) => { + await getHttpUserClient().putAvatar( + timeline.owner.username, + file + ); + onReload(); + }} + /> + ); + } + })()} + + ); +}; + +export default UserCard; diff --git a/FrontEnd/src/app/views/user/UserInfoCard.tsx b/FrontEnd/src/app/views/user/UserInfoCard.tsx deleted file mode 100644 index 24b7b979..00000000 --- a/FrontEnd/src/app/views/user/UserInfoCard.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; - -import TimelineCardTemplate, { - TimelineCardTemplateProps, -} from "../timeline-common/TimelineCardTemplate"; -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import UserAvatar from "../common/user/UserAvatar"; - -export type PersonalTimelineManageItem = "avatar" | "nickname"; - -export type UserInfoCardProps = TimelineCardComponentProps; - -const UserInfoCard: React.FC = (props) => { - const { timeline, operations } = props; - const { onManage, onMember } = operations; - - return ( - -

- {timeline.title} - {timeline.name} -

-
- - {timeline.owner.nickname} -
- - } - manageArea={((): TimelineCardTemplateProps["manageArea"] => { - if (onManage == null) { - return { type: "member", onMember }; - } else { - return { - type: "manage", - items: [ - { - type: "button", - text: "timeline.manageItem.nickname", - onClick: () => onManage("nickname"), - }, - { - type: "button", - text: "timeline.manageItem.avatar", - onClick: () => onManage("avatar"), - }, - { - type: "button", - text: "timeline.manageItem.property", - onClick: () => onManage("property"), - }, - { - type: "button", - onClick: onMember, - text: "timeline.manageItem.member", - }, - ], - }; - } - })()} - {...props} - /> - ); -}; - -export default UserInfoCard; diff --git a/FrontEnd/src/app/views/user/UserPageUI.tsx b/FrontEnd/src/app/views/user/UserPageUI.tsx deleted file mode 100644 index d405399c..00000000 --- a/FrontEnd/src/app/views/user/UserPageUI.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -import TimelinePageTemplateUI, { - TimelinePageTemplateUIProps, -} from "../timeline-common/TimelinePageTemplateUI"; - -import UserInfoCard, { PersonalTimelineManageItem } from "./UserInfoCard"; - -export type UserPageUIProps = Omit< - TimelinePageTemplateUIProps, - "CardComponent" ->; - -const UserPageUI: React.FC = (props) => { - return ; -}; - -export default UserPageUI; diff --git a/FrontEnd/src/app/views/user/index.tsx b/FrontEnd/src/app/views/user/index.tsx index 9b5acbba..57454d0d 100644 --- a/FrontEnd/src/app/views/user/index.tsx +++ b/FrontEnd/src/app/views/user/index.tsx @@ -1,58 +1,24 @@ -import React, { useState } from "react"; +import React from "react"; import { useParams } from "react-router"; -import { getHttpUserClient } from "@/http/user"; - import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate"; -import UserPageUI from "./UserPageUI"; -import { PersonalTimelineManageItem } from "./UserInfoCard"; -import ChangeNicknameDialog from "./ChangeNicknameDialog"; -import ChangeAvatarDialog from "./ChangeAvatarDialog"; +import UserCard from "./UserCard"; -const UserPage: React.FC = (_) => { +const UserPage: React.FC = () => { const { username } = useParams<{ username: string }>(); - const [dialog, setDialog] = useState(null); - const [reloadKey, setReloadKey] = React.useState(0); let dialogElement: React.ReactElement | undefined; - const closeDialog = (): void => setDialog(null); - - if (dialog === "nickname") { - dialogElement = ( - { - await getHttpUserClient().patch(username, { nickname: newNickname }); - setReloadKey(reloadKey + 1); - }} - /> - ); - } else if (dialog === "avatar") { - dialogElement = ( - { - await getHttpUserClient().putAvatar(username, file); - setReloadKey(reloadKey + 1); - }} - /> - ); - } - return ( <> setDialog(item)} + timelineName={`@${username}`} notFoundI18nKey="timeline.userNotExist" reloadKey={reloadKey} onReload={() => setReloadKey(reloadKey + 1)} + CardComponent={UserCard} /> {dialogElement} -- cgit v1.2.3