From 328ec15a1d4d927c53c4b9da4219d58ca12a2f20 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 21:02:56 +0800 Subject: chore: Fix code format. --- FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'FrontEnd/src/app/views/timeline') diff --git a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx index 1070c6d7..934ad090 100644 --- a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx +++ b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx @@ -11,9 +11,7 @@ import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; export type OrdinaryTimelineManageItem = "delete"; -export type TimelineInfoCardProps = TimelineCardComponentProps< - OrdinaryTimelineManageItem ->; +export type TimelineInfoCardProps = TimelineCardComponentProps; const TimelineInfoCard: React.FC = (props) => { const { -- cgit v1.2.3 From d960fdcfb79caf257fed6b68cc169a785003d965 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 21 Dec 2020 17:33:16 +0800 Subject: ... --- FrontEnd/src/app/http/bookmark.ts | 70 ++++++++++++++++++++++ FrontEnd/src/app/index.sass | 9 ++- .../views/timeline-common/TimelinePageTemplate.tsx | 14 +++++ .../timeline-common/TimelinePageTemplateUI.tsx | 4 ++ .../src/app/views/timeline/TimelineInfoCard.tsx | 12 +++- FrontEnd/src/app/views/user/UserInfoCard.tsx | 12 +++- 6 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 FrontEnd/src/app/http/bookmark.ts (limited to 'FrontEnd/src/app/views/timeline') diff --git a/FrontEnd/src/app/http/bookmark.ts b/FrontEnd/src/app/http/bookmark.ts new file mode 100644 index 00000000..68de4d73 --- /dev/null +++ b/FrontEnd/src/app/http/bookmark.ts @@ -0,0 +1,70 @@ +import axios from "axios"; + +import { + apiBaseUrl, + convertToNetworkError, + extractResponseData, +} from "./common"; + +import { + HttpTimelineInfo, + processRawTimelineInfo, + RawHttpTimelineInfo, +} from "./timeline"; + +export interface HttpHighlightMoveRequest { + timeline: string; + newPosition: number; +} + +export interface IHttpBookmarkClient { + list(token: string): Promise; + put(timeline: string, token: string): Promise; + delete(timeline: string, token: string): Promise; + move(req: HttpHighlightMoveRequest, token: string): Promise; +} + +export class HttpHighlightClient implements IHttpBookmarkClient { + list(token: string): Promise { + return axios + .get(`${apiBaseUrl}/bookmarks?token=${token}`) + .then(extractResponseData) + .then((list) => list.map(processRawTimelineInfo)) + .catch(convertToNetworkError); + } + + put(timeline: string, token: string): Promise { + return axios + .put(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`) + .catch(convertToNetworkError) + .then(); + } + + delete(timeline: string, token: string): Promise { + return axios + .delete(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`) + .catch(convertToNetworkError) + .then(); + } + + move(req: HttpHighlightMoveRequest, token: string): Promise { + return axios + .post(`${apiBaseUrl}/bookmarkop/move?token=${token}`, req) + .catch(convertToNetworkError) + .then(); + } +} + +let client: IHttpBookmarkClient = new HttpHighlightClient(); + +export function getHttpBookmarkClient(): IHttpBookmarkClient { + return client; +} + +export function setHttpBookmarkClient( + newClient: IHttpBookmarkClient +): IHttpBookmarkClient { + const old = client; + client = newClient; + return old; +} diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index 6325895a..ae315715 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -34,10 +34,12 @@ small width: 40px .icon-button - font-size: 1.4em + width: 1.4em + height: 1.4em cursor: pointer &.large - font-size: 1.6em + width: 1.6em + height: 1.6em .cursor-pointer cursor: pointer @@ -69,6 +71,9 @@ textarea .text-orange color: $orange +.text-yellow + color: $yellow + @each $color, $value in $theme-colors .text-button background: transparent diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 6c57e91d..3833cdd9 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -11,6 +11,7 @@ import { usePostList, useTimelineInfo, } from "@/services/timeline"; +import { getHttpBookmarkClient } from "@/http/bookmark"; import { TimelineMemberDialog } from "./TimelineMember"; import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog"; @@ -116,6 +117,19 @@ export default function TimelinePageTemplate( ? onManage : undefined, onMember: () => setDialog("member"), + onBookmark: + user != null + ? () => { + void getHttpBookmarkClient() + .put(name, user.token) + .then(() => { + pushAlert({ + message: "Succeeded to add bookmark!", //TODO: i18n + type: "success", + }); + }); + } + : undefined, }; if (type === "cache") { diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx index f60383dd..b7cd4a45 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx @@ -15,6 +15,8 @@ export interface TimelineCardComponentProps { timeline: TimelineInfo; onManage?: (item: TManageItems | "property") => void; onMember: () => void; + onBookmark?: () => void; + onHighlight?: () => void; className?: string; collapse: boolean; syncStatus: TimelineSyncStatus; @@ -28,6 +30,7 @@ export interface TimelinePageTemplateUIProps { posts?: TimelinePostInfoEx[]; onManage?: (item: TManageItems | "property") => void; onMember: () => void; + onBookmark?: () => void; onPost?: TimelinePostSendCallback; } | I18nText; @@ -153,6 +156,7 @@ export default function TimelinePageTemplateUI( timeline={data.timeline} onManage={data.onManage} onMember={data.onMember} + onBookmark={data.onBookmark} syncStatus={syncStatus} collapse={cardCollapse} toggleCollapse={toggleCardCollapse} diff --git a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx index 934ad090..8f967a34 100644 --- a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx +++ b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx @@ -1,6 +1,8 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Dropdown, Button } from "react-bootstrap"; +import Svg from "react-inlinesvg"; +import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; import { useAvatar } from "@/services/user"; import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; @@ -18,6 +20,7 @@ const TimelineInfoCard: React.FC = (props) => { timeline, collapse, onMember, + onBookmark, onManage, syncStatus, toggleCollapse, @@ -50,8 +53,15 @@ const TimelineInfoCard: React.FC = (props) => { {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
+ {onBookmark != null ? ( + + ) : null} {onManage != null ? ( - + {t("timeline.manage")} diff --git a/FrontEnd/src/app/views/user/UserInfoCard.tsx b/FrontEnd/src/app/views/user/UserInfoCard.tsx index 3ba1c96e..0e1e093a 100644 --- a/FrontEnd/src/app/views/user/UserInfoCard.tsx +++ b/FrontEnd/src/app/views/user/UserInfoCard.tsx @@ -1,6 +1,8 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Dropdown, Button } from "react-bootstrap"; +import Svg from "react-inlinesvg"; +import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; import { useAvatar } from "@/services/user"; @@ -19,6 +21,7 @@ const UserInfoCard: React.FC = (props) => { collapse, onMember, onManage, + onBookmark, syncStatus, toggleCollapse, } = props; @@ -46,8 +49,15 @@ const UserInfoCard: React.FC = (props) => { {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
+ {onBookmark != null ? ( + + ) : null} {onManage != null ? ( - + {t("timeline.manage")} -- cgit v1.2.3 From a337ce43ae91c0c9a1c359dbb91faf75f1375505 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 22 Dec 2020 00:27:33 +0800 Subject: ... --- .../app/views/timeline-common/InfoCardTemplate.tsx | 26 ----- .../views/timeline-common/TimelineCardTemplate.tsx | 100 ++++++++++++++++ .../src/app/views/timeline/TimelineInfoCard.tsx | 128 +++++++++------------ FrontEnd/src/app/views/user/UserInfoCard.tsx | 123 +++++++++----------- 4 files changed, 208 insertions(+), 169 deletions(-) delete mode 100644 FrontEnd/src/app/views/timeline-common/InfoCardTemplate.tsx create mode 100644 FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx (limited to 'FrontEnd/src/app/views/timeline') diff --git a/FrontEnd/src/app/views/timeline-common/InfoCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/InfoCardTemplate.tsx deleted file mode 100644 index a8de20aa..00000000 --- a/FrontEnd/src/app/views/timeline-common/InfoCardTemplate.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import clsx from "clsx"; - -import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import SyncStatusBadge from "../timeline-common/SyncStatusBadge"; -import CollapseButton from "../timeline-common/CollapseButton"; - -const InfoCardTemplate: React.FC< - Pick< - TimelineCardComponentProps<"">, - "collapse" | "toggleCollapse" | "syncStatus" | "className" - > & { children: React.ReactElement[] } -> = ({ collapse, toggleCollapse, syncStatus, className, children }) => { - return ( -
-
- - -
- -
{children}
-
- ); -}; - -export default InfoCardTemplate; diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx new file mode 100644 index 00000000..a47b3d76 --- /dev/null +++ b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx @@ -0,0 +1,100 @@ +import React from "react"; +import clsx from "clsx"; +import { useTranslation } from "react-i18next"; +import { Dropdown, Button } from "react-bootstrap"; +import Svg from "react-inlinesvg"; +import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; + +import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; + +import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; +import SyncStatusBadge from "../timeline-common/SyncStatusBadge"; +import CollapseButton from "../timeline-common/CollapseButton"; + +export interface TimelineCardTemplateProps + extends Omit, "onManage" | "onMember"> { + 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, + onBookmark, + toggleCollapse, + syncStatus, + className, +}: TimelineCardTemplateProps): React.ReactElement | null { + const { t } = useTranslation(); + + return ( +
+
+ + +
+
+ {infoArea} +

{timeline.description}

+ + {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} + +
+ {onBookmark != null ? ( + + ) : 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/TimelineInfoCard.tsx b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx index 8f967a34..f4dbb67d 100644 --- a/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx +++ b/FrontEnd/src/app/views/timeline/TimelineInfoCard.tsx @@ -1,93 +1,73 @@ import React from "react"; -import { useTranslation } from "react-i18next"; -import { Dropdown, Button } from "react-bootstrap"; -import Svg from "react-inlinesvg"; -import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; import { useAvatar } from "@/services/user"; -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; import BlobImage from "../common/BlobImage"; +import TimelineCardTemplate, { + TimelineCardTemplateProps, +} from "../timeline-common/TimelineCardTemplate"; import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; export type OrdinaryTimelineManageItem = "delete"; export type TimelineInfoCardProps = TimelineCardComponentProps; const TimelineInfoCard: React.FC = (props) => { - const { - timeline, - collapse, - onMember, - onBookmark, - onManage, - syncStatus, - toggleCollapse, - } = props; - - const { t } = useTranslation(); + const { onMember, onManage, ...otherProps } = props; + const { timeline } = props; const avatar = useAvatar(timeline?.owner?.username); return ( - -

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

-
- - {timeline.owner.nickname} - - @{timeline.owner.username} - -
-

{timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - -
- {onBookmark != null ? ( - - ) : null} - {onManage != null ? ( - - - {t("timeline.manage")} - - - onManage("property")}> - {t("timeline.manageItem.property")} - - - {t("timeline.manageItem.member")} - - - onManage("delete")} - > - {t("timeline.manageItem.delete")} - - - - ) : ( - - )} -
-
+ +

+ {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", + }, + ], + }; + } + })()} + {...otherProps} + /> ); }; diff --git a/FrontEnd/src/app/views/user/UserInfoCard.tsx b/FrontEnd/src/app/views/user/UserInfoCard.tsx index 0e1e093a..f31a939f 100644 --- a/FrontEnd/src/app/views/user/UserInfoCard.tsx +++ b/FrontEnd/src/app/views/user/UserInfoCard.tsx @@ -1,88 +1,73 @@ import React from "react"; -import { useTranslation } from "react-i18next"; -import { Dropdown, Button } from "react-bootstrap"; -import Svg from "react-inlinesvg"; -import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; import { useAvatar } from "@/services/user"; import BlobImage from "../common/BlobImage"; +import TimelineCardTemplate, { + TimelineCardTemplateProps, +} from "../timeline-common/TimelineCardTemplate"; import { TimelineCardComponentProps } from "../timeline-common/TimelinePageTemplateUI"; -import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; export type PersonalTimelineManageItem = "avatar" | "nickname"; export type UserInfoCardProps = TimelineCardComponentProps; const UserInfoCard: React.FC = (props) => { - const { - timeline, - collapse, - onMember, - onManage, - onBookmark, - syncStatus, - toggleCollapse, - } = props; - const { t } = useTranslation(); + const { onMember, onManage, ...otherProps } = props; + const { timeline } = props; const avatar = useAvatar(timeline?.owner?.username); return ( - -

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

-
- - {timeline.owner.nickname} -
-

{timeline.description}

- - {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - -
- {onBookmark != null ? ( - - ) : null} - {onManage != null ? ( - - - {t("timeline.manage")} - - - onManage("nickname")}> - {t("timeline.manageItem.nickname")} - - onManage("avatar")}> - {t("timeline.manageItem.avatar")} - - onManage("property")}> - {t("timeline.manageItem.property")} - - - {t("timeline.manageItem.member")} - - - - ) : ( - - )} -
-
+ +

+ {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", + }, + ], + }; + } + })()} + {...otherProps} + /> ); }; -- cgit v1.2.3