From 0e6bff709af16d6196b5cef17cc8a8f69af97cca Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 20:58:56 +0800 Subject: fix: Fix code to adapt breaking change in workbox. --- FrontEnd/src/app/service-worker.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/service-worker.tsx b/FrontEnd/src/app/service-worker.tsx index 3be54bc1..2dc7fb6e 100644 --- a/FrontEnd/src/app/service-worker.tsx +++ b/FrontEnd/src/app/service-worker.tsx @@ -70,7 +70,9 @@ if ("serviceWorker" in navigator) { } }); - const showSkipWaitingPrompt = (): void => { + // Add an event listener to detect when the registered + // service worker has installed but is waiting to activate. + wb.addEventListener("waiting", (): void => { const upgrade = (): void => { isThisTriggerUpgrade = true; if (registration && registration.waiting) { @@ -99,12 +101,7 @@ if ("serviceWorker" in navigator) { dismissTime: "never", type: "success", }); - }; - - // Add an event listener to detect when the registered - // service worker has installed but is waiting to activate. - wb.addEventListener("waiting", showSkipWaitingPrompt); - wb.addEventListener("externalwaiting", showSkipWaitingPrompt); + }); void wb.register().then((reg) => { registration = reg; -- cgit v1.2.3 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/admin/UserAdmin.tsx | 37 ++++++++++++---------- FrontEnd/src/app/views/common/OperationDialog.tsx | 8 ++--- .../src/app/views/timeline/TimelineInfoCard.tsx | 4 +-- FrontEnd/src/app/views/user/UserInfoCard.tsx | 4 +-- 4 files changed, 24 insertions(+), 29 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx index 948cbb25..e1d2c3da 100644 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx @@ -62,10 +62,9 @@ const UsernameLabel: React.FC = (props) => { return {props.children}; }; -const UserDeleteDialog: React.FC> = ({ open, close, token, data: { username }, onSuccess }) => { +const UserDeleteDialog: React.FC< + DialogProps<{ username: string }, unknown> +> = ({ open, close, token, data: { username }, onSuccess }) => { return ( > = ({ open, close, token, data: { oldUser }, onSuccess }) => { +const UserModifyDialog: React.FC< + DialogProps< + { + oldUser: HttpUser; + }, + HttpUser + > +> = ({ open, close, token, data: { oldUser }, onSuccess }) => { return ( > = ({ open, close, token, data: { username, permissions }, onSuccess }) => { +const UserPermissionModifyDialog: React.FC< + DialogProps< + { + username: string; + permissions: UserPermission[]; + }, + UserPermission[] + > +> = ({ open, close, token, data: { username, permissions }, onSuccess }) => { const oldPermissionBoolList: boolean[] = kUserPermissionList.map( (permission) => permissions.includes(permission) ); diff --git a/FrontEnd/src/app/views/common/OperationDialog.tsx b/FrontEnd/src/app/views/common/OperationDialog.tsx index 77ed851f..5887be48 100644 --- a/FrontEnd/src/app/views/common/OperationDialog.tsx +++ b/FrontEnd/src/app/views/common/OperationDialog.tsx @@ -172,9 +172,7 @@ const OperationDialog = < setStep("process"); props .onProcess( - (values as unknown) as MapOperationInputInfoValueTypeList< - OperationInputInfoList - > + (values as unknown) as MapOperationInputInfoValueTypeList ) .then( (d) => { @@ -206,9 +204,7 @@ const OperationDialog = < const { inputValidator } = props; if (inputValidator != null) { const result = inputValidator( - (values as unknown) as MapOperationInputInfoValueTypeList< - OperationInputInfoList - > + (values as unknown) as MapOperationInputInfoValueTypeList ); setInputError(result); return isNoError(result); 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 { diff --git a/FrontEnd/src/app/views/user/UserInfoCard.tsx b/FrontEnd/src/app/views/user/UserInfoCard.tsx index 4a0c9e87..3ba1c96e 100644 --- a/FrontEnd/src/app/views/user/UserInfoCard.tsx +++ b/FrontEnd/src/app/views/user/UserInfoCard.tsx @@ -11,9 +11,7 @@ import InfoCardTemplate from "../timeline-common/InfoCardTemplate"; export type PersonalTimelineManageItem = "avatar" | "nickname"; -export type UserInfoCardProps = TimelineCardComponentProps< - PersonalTimelineManageItem ->; +export type UserInfoCardProps = TimelineCardComponentProps; const UserInfoCard: React.FC = (props) => { const { -- cgit v1.2.3 From 0751d07c55dd26a8bc825e695bfa6c6442857b0b Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 21:17:05 +0800 Subject: feat: Add highlight timeline http client. --- FrontEnd/src/app/http/highlight.ts | 70 ++++++++++++++++++++++++++++++++++++++ FrontEnd/src/app/http/timeline.ts | 14 ++++---- 2 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 FrontEnd/src/app/http/highlight.ts (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/http/highlight.ts b/FrontEnd/src/app/http/highlight.ts new file mode 100644 index 00000000..1f226c19 --- /dev/null +++ b/FrontEnd/src/app/http/highlight.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 IHttpHighlightClient { + list(): Promise; + put(timeline: string, token: string): Promise; + delete(timeline: string, token: string): Promise; + move(req: HttpHighlightMoveRequest, token: string): Promise; +} + +export class HttpHighlightClient implements IHttpHighlightClient { + list(): Promise { + return axios + .get(`${apiBaseUrl}/highlights`) + .then(extractResponseData) + .then((list) => list.map(processRawTimelineInfo)) + .catch(convertToNetworkError); + } + + put(timeline: string, token: string): Promise { + return axios + .put(`${apiBaseUrl}/highlights/${timeline}?token=${token}`) + .catch(convertToNetworkError) + .then(); + } + + delete(timeline: string, token: string): Promise { + return axios + .delete(`${apiBaseUrl}/highlights/${timeline}?token=${token}`) + .catch(convertToNetworkError) + .then(); + } + + move(req: HttpHighlightMoveRequest, token: string): Promise { + return axios + .post(`${apiBaseUrl}/highlightop/move?token=${token}`, req) + .catch(convertToNetworkError) + .then(); + } +} + +let client: IHttpHighlightClient = new HttpHighlightClient(); + +export function getHttpHighlightClient(): IHttpHighlightClient { + return client; +} + +export function setHttpHighlightClient( + newClient: IHttpHighlightClient +): IHttpHighlightClient { + const old = client; + client = newClient; + return old; +} diff --git a/FrontEnd/src/app/http/timeline.ts b/FrontEnd/src/app/http/timeline.ts index 71c49852..6be0a183 100644 --- a/FrontEnd/src/app/http/timeline.ts +++ b/FrontEnd/src/app/http/timeline.ts @@ -121,7 +121,7 @@ export class HttpTimelineNameConflictError extends Error { //-------------------- begin: internal model -------------------- -interface RawTimelineInfo { +export interface RawHttpTimelineInfo { uniqueId: string; title: string; name: string; @@ -188,7 +188,9 @@ interface RawTimelinePostPostRequest { //-------------------- end: internal model -------------------- -function processRawTimelineInfo(raw: RawTimelineInfo): HttpTimelineInfo { +export function processRawTimelineInfo( + raw: RawHttpTimelineInfo +): HttpTimelineInfo { return { ...raw, lastModified: new Date(raw.lastModified), @@ -293,7 +295,7 @@ export interface IHttpTimelineClient { export class HttpTimelineClient implements IHttpTimelineClient { listTimeline(query: HttpTimelineListQuery): Promise { return axios - .get( + .get( applyQueryParameters(`${apiBaseUrl}/timelines`, query) ) .then(extractResponseData) @@ -323,7 +325,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { } ): Promise { return axios - .get( + .get( applyQueryParameters(`${apiBaseUrl}/timelines/${timelineName}`, query) ) .then((res) => { @@ -342,7 +344,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { token: string ): Promise { return axios - .post(`${apiBaseUrl}/timelines?token=${token}`, req) + .post(`${apiBaseUrl}/timelines?token=${token}`, req) .then(extractResponseData) .then(processRawTimelineInfo) .catch(convertToIfErrorCodeIs(11040101, HttpTimelineNameConflictError)) @@ -355,7 +357,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { token: string ): Promise { return axios - .patch( + .patch( `${apiBaseUrl}/timelines/${timelineName}?token=${token}`, req ) -- cgit v1.2.3 From a7ef774136682d6af13d4ece202a0bb94508c36b Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 21:35:47 +0800 Subject: feat: Remove highlight from admin. --- FrontEnd/src/app/locales/en/admin.json | 2 +- FrontEnd/src/app/locales/zh/admin.json | 2 +- FrontEnd/src/app/views/admin/Admin.tsx | 6 +++--- FrontEnd/src/app/views/admin/AdminNav.tsx | 6 +++--- FrontEnd/src/app/views/admin/HighlightTimelineAdmin.tsx | 13 ------------- FrontEnd/src/app/views/admin/MoreAdmin.tsx | 13 +++++++++++++ 6 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 FrontEnd/src/app/views/admin/HighlightTimelineAdmin.tsx create mode 100644 FrontEnd/src/app/views/admin/MoreAdmin.tsx (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/admin.json b/FrontEnd/src/app/locales/en/admin.json index 098ffb1f..ddb3ffad 100644 --- a/FrontEnd/src/app/locales/en/admin.json +++ b/FrontEnd/src/app/locales/en/admin.json @@ -1,7 +1,7 @@ { "nav": { "users": "Users", - "highlightTimelines": "Highlight Timelines" + "more": "More" }, "create": "Create", "user": { diff --git a/FrontEnd/src/app/locales/zh/admin.json b/FrontEnd/src/app/locales/zh/admin.json index fed39b2d..edd1cabd 100644 --- a/FrontEnd/src/app/locales/zh/admin.json +++ b/FrontEnd/src/app/locales/zh/admin.json @@ -1,7 +1,7 @@ { "nav": { "users": "用户", - "highlightTimelines": "高光时间线" + "more": "更多" }, "create": "创建", "user": { diff --git a/FrontEnd/src/app/views/admin/Admin.tsx b/FrontEnd/src/app/views/admin/Admin.tsx index 446cd36d..0b6d1f05 100644 --- a/FrontEnd/src/app/views/admin/Admin.tsx +++ b/FrontEnd/src/app/views/admin/Admin.tsx @@ -7,7 +7,7 @@ import { AuthUser } from "@/services/user"; import AdminNav from "./AdminNav"; import UserAdmin from "./UserAdmin"; -import HighlightTimelineAdmin from "./HighlightTimelineAdmin"; +import MoreAdmin from "./MoreAdmin"; interface AdminProps { user: AuthUser; @@ -32,8 +32,8 @@ const Admin: React.FC = ({ user }) => { {(() => { if (name === "users") { return ; - } else if (name === "highlighttimelines") { - return ; + } else if (name === "more") { + return ; } })()} diff --git a/FrontEnd/src/app/views/admin/AdminNav.tsx b/FrontEnd/src/app/views/admin/AdminNav.tsx index f376beda..47e2138f 100644 --- a/FrontEnd/src/app/views/admin/AdminNav.tsx +++ b/FrontEnd/src/app/views/admin/AdminNav.tsx @@ -29,12 +29,12 @@ const AdminNav: React.FC = () => { { - toggle("highlighttimelines"); + toggle("more"); }} > - {t("admin:nav.highlightTimelines")} + {t("admin:nav.more")} diff --git a/FrontEnd/src/app/views/admin/HighlightTimelineAdmin.tsx b/FrontEnd/src/app/views/admin/HighlightTimelineAdmin.tsx deleted file mode 100644 index 3de7d5a6..00000000 --- a/FrontEnd/src/app/views/admin/HighlightTimelineAdmin.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -import { AuthUser } from "@/services/user"; - -export interface HighlightTimelineAdminProps { - user: AuthUser; -} - -const HighlightTimelineAdmin: React.FC = () => { - return <>This is highlight timeline administration page.; -}; - -export default HighlightTimelineAdmin; diff --git a/FrontEnd/src/app/views/admin/MoreAdmin.tsx b/FrontEnd/src/app/views/admin/MoreAdmin.tsx new file mode 100644 index 00000000..042789a0 --- /dev/null +++ b/FrontEnd/src/app/views/admin/MoreAdmin.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import { AuthUser } from "@/services/user"; + +export interface MoreAdminProps { + user: AuthUser; +} + +const MoreAdmin: React.FC = () => { + return <>More...; +}; + +export default MoreAdmin; -- cgit v1.2.3 From b1fcdac173457d2462836a8a749f3b2653be0e3a Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 21:42:58 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 1 + FrontEnd/src/app/locales/zh/translation.json | 1 + FrontEnd/src/app/views/home/BoardWithUser.tsx | 70 +++++++-------------------- 3 files changed, 19 insertions(+), 53 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index cdb6da37..f07efafe 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -21,6 +21,7 @@ "home": { "go": "Go!", "allTimeline": "All Timelines", + "relatedTimeline": "Timelines Related To You", "joinTimeline": "Joined Timelines", "ownTimeline": "Owned Timelines", "offlinePrompt": "Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here to refresh.", diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 5d28f694..5991f3cd 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -21,6 +21,7 @@ "home": { "go": "冲!", "allTimeline": "所有的时间线", + "relatedTimeline": "关于你的时间线", "joinTimeline": "加入的时间线", "ownTimeline": "拥有的时间线", "offlinePrompt": "你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。", diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index bbef835a..16648820 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -12,87 +12,51 @@ import OfflineBoard from "./OfflineBoard"; const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { const { t } = useTranslation(); - const [ownTimelines, setOwnTimelines] = React.useState< + const [relatedTimelines, setRelatedTimelines] = React.useState< TimelineInfo[] | "offline" | "loading" >("loading"); - const [joinTimelines, setJoinTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - - React.useEffect(() => { - let subscribe = true; - if (ownTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ relate: user.username, relateType: "own" }) - .then( - (timelines) => { - if (subscribe) { - setOwnTimelines(timelines); - } - }, - () => { - setOwnTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [user, ownTimelines]); React.useEffect(() => { let subscribe = true; - if (joinTimelines === "loading") { + if (relatedTimelines === "loading") { void getHttpTimelineClient() - .listTimeline({ relate: user.username, relateType: "join" }) + .listTimeline({ relate: user.username }) .then( (timelines) => { if (subscribe) { - setJoinTimelines(timelines); + setRelatedTimelines(timelines); } }, () => { - setJoinTimelines("offline"); + setRelatedTimelines("offline"); } ); } return () => { subscribe = false; }; - }, [user, joinTimelines]); + }, [user, relatedTimelines]); return ( - {ownTimelines === "offline" && joinTimelines === "offline" ? ( + {relatedTimelines === "offline" ? ( { - setOwnTimelines("loading"); - setJoinTimelines("loading"); + setRelatedTimelines("loading"); }} /> ) : ( - <> - - { - setOwnTimelines("loading"); - }} - /> - - - { - setJoinTimelines("loading"); - }} - /> - - + + { + setRelatedTimelines("loading"); + }} + /> + )} ); -- cgit v1.2.3 From 59b54e6e838b0b0f343ea917815f342b2987c417 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 22:04:25 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 5 +- FrontEnd/src/app/locales/zh/translation.json | 5 +- FrontEnd/src/app/views/home/BoardWithUser.tsx | 61 ++++++------------------ FrontEnd/src/app/views/home/BoardWithoutUser.tsx | 61 +++++++----------------- FrontEnd/src/app/views/home/TimelineBoard.tsx | 50 ++++++++++++++++++- 5 files changed, 84 insertions(+), 98 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index f07efafe..be80d21e 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -20,10 +20,9 @@ "loadImageError": "Failed to load image.", "home": { "go": "Go!", - "allTimeline": "All Timelines", + "highlightTimeline": "Highlight Timelines", "relatedTimeline": "Timelines Related To You", - "joinTimeline": "Joined Timelines", - "ownTimeline": "Owned Timelines", + "publicTimeline": "Public Timelines", "offlinePrompt": "Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here to refresh.", "createButton": "Create Timeline", "createDialog": { diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 5991f3cd..8c925eb9 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -20,10 +20,9 @@ "loadImageError": "加载图片失败", "home": { "go": "冲!", - "allTimeline": "所有的时间线", + "highlightTimeline": "高光时间线", "relatedTimeline": "关于你的时间线", - "joinTimeline": "加入的时间线", - "ownTimeline": "拥有的时间线", + "publicTimeline": "公开时间线", "offlinePrompt": "你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。", "createButton": "创建时间线", "createDialog": { diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 16648820..9b2f395d 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -3,61 +3,30 @@ import { Row, Col } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { AuthUser } from "@/services/user"; -import { TimelineInfo } from "@/services/timeline"; +import { getHttpHighlightClient } from "@/http/highlight"; import { getHttpTimelineClient } from "@/http/timeline"; import TimelineBoard from "./TimelineBoard"; -import OfflineBoard from "./OfflineBoard"; const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { const { t } = useTranslation(); - const [relatedTimelines, setRelatedTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - - React.useEffect(() => { - let subscribe = true; - if (relatedTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ relate: user.username }) - .then( - (timelines) => { - if (subscribe) { - setRelatedTimelines(timelines); - } - }, - () => { - setRelatedTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [user, relatedTimelines]); - return ( - {relatedTimelines === "offline" ? ( - - { - setRelatedTimelines("loading"); - }} - /> - - ) : ( - - { - setRelatedTimelines("loading"); - }} - /> - - )} + + getHttpHighlightClient().list()} + /> + + + + getHttpTimelineClient().listTimeline({ relate: user.username }) + } + /> + ); }; diff --git a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx b/FrontEnd/src/app/views/home/BoardWithoutUser.tsx index 7e30f799..ad88af7a 100644 --- a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithoutUser.tsx @@ -1,58 +1,31 @@ import React from "react"; import { Row, Col } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; -import { TimelineInfo } from "@/services/timeline"; +import { getHttpHighlightClient } from "@/http/highlight"; import { getHttpTimelineClient } from "@/http/timeline"; import TimelineBoard from "./TimelineBoard"; -import OfflineBoard from "./OfflineBoard"; const BoardWithoutUser: React.FC = () => { - const [publicTimelines, setPublicTimelines] = React.useState< - TimelineInfo[] | "offline" | "loading" - >("loading"); - - React.useEffect(() => { - let subscribe = true; - if (publicTimelines === "loading") { - void getHttpTimelineClient() - .listTimeline({ visibility: "Public" }) - .then( - (timelines) => { - if (subscribe) { - setPublicTimelines(timelines); - } - }, - () => { - setPublicTimelines("offline"); - } - ); - } - return () => { - subscribe = false; - }; - }, [publicTimelines]); + const { t } = useTranslation(); return ( - {publicTimelines === "offline" ? ( - - { - setPublicTimelines("loading"); - }} - /> - - ) : ( - - { - setPublicTimelines("loading"); - }} - /> - - )} + + getHttpHighlightClient().list()} + /> + + + + getHttpTimelineClient().listTimeline({ visibility: "Public" }) + } + /> + ); }; diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index c2a7e5fe..ae7783e6 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -8,14 +8,14 @@ import { TimelineInfo } from "@/services/timeline"; import TimelineLogo from "../common/TimelineLogo"; import UserTimelineLogo from "../common/UserTimelineLogo"; -export interface TimelineBoardProps { +interface TimelineBoardUIProps { title?: string; timelines: TimelineInfo[] | "offline" | "loading"; onReload: () => void; className?: string; } -const TimelineBoard: React.FC = (props) => { +const TimelineBoardUI: React.FC = (props) => { const { title, timelines, className } = props; return ( @@ -71,4 +71,50 @@ const TimelineBoard: React.FC = (props) => { ); }; +export interface TimelineBoardProps { + title?: string; + className?: string; + load: () => Promise; +} + +const TimelineBoard: React.FC = ({ + className, + title, + load, +}) => { + const [timelines, setTimelines] = React.useState< + TimelineInfo[] | "offline" | "loading" + >("loading"); + + React.useEffect(() => { + let subscribe = true; + if (timelines === "loading") { + void load().then( + (timelines) => { + if (subscribe) { + setTimelines(timelines); + } + }, + () => { + setTimelines("offline"); + } + ); + } + return () => { + subscribe = false; + }; + }, [load, timelines]); + + return ( + { + setTimelines("loading"); + }} + /> + ); +}; + export default TimelineBoard; -- cgit v1.2.3 From caa9d638c8167a54e19344e6e4cea99da2b031ef Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 19 Dec 2020 22:15:05 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 7 +++++-- FrontEnd/src/app/views/home/BoardWithUser.tsx | 4 ++-- FrontEnd/src/app/views/home/BoardWithoutUser.tsx | 4 ++-- FrontEnd/src/app/views/home/index.tsx | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index d5e1ea22..6325895a 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -50,10 +50,13 @@ textarea .cru-card @extend .shadow - @extend .border @extend .rounded + border: 1px solid border-color: $gray-200 - background: $light + background: $gray-100 + transition: all 0.3s + &:hover + border-color: $primary .full-viewport-center-child position: fixed diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 9b2f395d..469eb388 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -13,13 +13,13 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { return ( - + getHttpHighlightClient().list()} /> - + diff --git a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx b/FrontEnd/src/app/views/home/BoardWithoutUser.tsx index ad88af7a..d9c7fcf4 100644 --- a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithoutUser.tsx @@ -12,13 +12,13 @@ const BoardWithoutUser: React.FC = () => { return ( - + getHttpHighlightClient().list()} /> - + diff --git a/FrontEnd/src/app/views/home/index.tsx b/FrontEnd/src/app/views/home/index.tsx index 0d439f36..3c53736d 100644 --- a/FrontEnd/src/app/views/home/index.tsx +++ b/FrontEnd/src/app/views/home/index.tsx @@ -33,7 +33,7 @@ const HomePage: React.FC = () => { return ( <> - + 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') 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') 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 From e89f8c73fe646e43f66a9f869b56267557c96df5 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 23 Dec 2020 22:35:11 +0800 Subject: ... --- FrontEnd/src/app/services/user.ts | 4 ++++ .../app/views/timeline-common/TimelineCardTemplate.tsx | 9 +++++++++ .../app/views/timeline-common/TimelinePageTemplate.tsx | 16 +++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/services/user.ts b/FrontEnd/src/app/services/user.ts index 0166bce0..7a60b474 100644 --- a/FrontEnd/src/app/services/user.ts +++ b/FrontEnd/src/app/services/user.ts @@ -43,6 +43,10 @@ export class AuthUser implements User { get hasAllTimelineAdministrationPermission(): boolean { return this.permissions.includes("AllTimelineManagement"); } + + get hasHighlightTimelineAdministrationPermission(): boolean { + return this.permissions.includes("HighlightTimelineManagement"); + } } export interface LoginCredentials { diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx index a47b3d76..ffec1a90 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx @@ -3,6 +3,7 @@ import clsx from "clsx"; import { useTranslation } from "react-i18next"; import { Dropdown, Button } from "react-bootstrap"; import Svg from "react-inlinesvg"; +import starIcon from "bootstrap-icons/icons/star.svg"; import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; @@ -36,6 +37,7 @@ function TimelineCardTemplate({ infoArea, manageArea, onBookmark, + onHighlight, toggleCollapse, syncStatus, className, @@ -55,6 +57,13 @@ function TimelineCardTemplate({ {t(timelineVisibilityTooltipTranslationMap[timeline.visibility])}
+ {onHighlight != null ? ( + + ) : null} {onBookmark != null ? ( ( .put(name, user.token) .then(() => { pushAlert({ - message: "Succeeded to add bookmark!", //TODO: i18n + message: "Succeeded to add bookmark!", // TODO: i18n + type: "success", + }); + }); + } + : undefined, + onHighlight: + user != null && user.hasHighlightTimelineAdministrationPermission + ? () => { + void getHttpHighlightClient() + .put(name, user.token) + .then(() => { + pushAlert({ + message: "Succeeded to add highlight!", // TODO: i18n type: "success", }); }); -- cgit v1.2.3 From fd2c7f598ef959be673c8fae3cb021c9c1c7ff63 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 24 Dec 2020 17:14:06 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 89 ++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 16 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index ae7783e6..87ed98e1 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -7,20 +7,90 @@ import { Spinner } from "react-bootstrap"; import { TimelineInfo } from "@/services/timeline"; import TimelineLogo from "../common/TimelineLogo"; import UserTimelineLogo from "../common/UserTimelineLogo"; +import { HttpTimelineInfo } from "@/http/timeline"; + +interface TimelineBoardItemProps { + timeline: HttpTimelineInfo; + // If not null, will disable navigation on click. + actions?: { + onDelete: () => void; + onMove: (e: React.MouseEvent) => void; + }; +} + +const TimelineBoardItem: React.FC = ({ + timeline, + actions, +}) => { + const { name, title } = timeline; + const isPersonal = name.startsWith("@"); + const url = isPersonal + ? `/users/${timeline.owner.username}` + : `/timelines/${name}`; + + const content = ( + <> + {isPersonal ? ( + + ) : ( + + )} + {title} + {name} + + ); + + return actions == null ? ( + + {content} + + ) : ( +
{content}
+ ); +}; interface TimelineBoardUIProps { title?: string; timelines: TimelineInfo[] | "offline" | "loading"; onReload: () => void; className?: string; + editHandler?: { + onDelete: (timeline: string) => Promise; + }; } const TimelineBoardUI: React.FC = (props) => { - const { title, timelines, className } = props; + const { title, timelines, className, editHandler } = props; + + const editable = editHandler != null; + + const [editing, setEditing] = React.useState(false); return (
- {title != null &&

{title}

} +
+ {title != null &&

{title}

} + { + editable && + (editing ? ( +
{ + setEditing(false); + }} + > + Done +
+ ) : ( +
{ + setEditing(true); + }} + > + Edit +
+ )) // TODO: i18n + } +
{(() => { if (timelines === "loading") { return ( @@ -48,21 +118,8 @@ const TimelineBoardUI: React.FC = (props) => { ); } else { return timelines.map((timeline) => { - const { name, title } = timeline; - const isPersonal = name.startsWith("@"); - const url = isPersonal - ? `/users/${timeline.owner.username}` - : `/timelines/${name}`; return ( - - {isPersonal ? ( - - ) : ( - - )} - {title} - {name} - + ); }); } -- cgit v1.2.3 From d9ac1c1d1d999b0e7719504ff5c3234915de8f06 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 31 Dec 2020 21:07:52 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 7 +++---- FrontEnd/src/app/views/admin/UserAdmin.tsx | 7 ++----- FrontEnd/src/app/views/timeline-common/CollapseButton.tsx | 12 ++++++------ .../src/app/views/timeline-common/TimelineCardTemplate.tsx | 13 ++++--------- FrontEnd/src/app/views/timeline-common/TimelineItem.tsx | 13 ++++--------- FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx | 11 +++++------ 6 files changed, 24 insertions(+), 39 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index ae315715..1a833174 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -1,4 +1,5 @@ @import '~bootstrap/scss/bootstrap' +@import '~bootstrap-icons/font/bootstrap-icons.css' @import './views/common/common' @import './views/common/alert/alert' @@ -34,12 +35,10 @@ small width: 40px .icon-button - width: 1.4em - height: 1.4em + font-size: 1.4em cursor: pointer &.large - width: 1.6em - height: 1.6em + font-size: 1.6em .cursor-pointer cursor: pointer diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx index e1d2c3da..d66abbec 100644 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx @@ -1,8 +1,6 @@ import React, { useState, useEffect } from "react"; import clsx from "clsx"; import { ListGroup, Row, Col, Spinner, Button } from "react-bootstrap"; -import InlineSVG from "react-inlinesvg"; -import PencilSquareIcon from "bootstrap-icons/icons/pencil-square.svg"; import OperationDialog, { OperationBoolInputInfo, @@ -220,9 +218,8 @@ const UserItem: React.FC = ({ user, on }) => { return ( - setEditMaskVisible(true)} />

{user.username}

diff --git a/FrontEnd/src/app/views/timeline-common/CollapseButton.tsx b/FrontEnd/src/app/views/timeline-common/CollapseButton.tsx index 3c52150f..da54f3fd 100644 --- a/FrontEnd/src/app/views/timeline-common/CollapseButton.tsx +++ b/FrontEnd/src/app/views/timeline-common/CollapseButton.tsx @@ -1,8 +1,5 @@ import React from "react"; import clsx from "clsx"; -import Svg from "react-inlinesvg"; -import arrowsAngleContractIcon from "bootstrap-icons/icons/arrows-angle-contract.svg"; -import arrowsAngleExpandIcon from "bootstrap-icons/icons/arrows-angle-expand.svg"; const CollapseButton: React.FC<{ collapse: boolean; @@ -11,10 +8,13 @@ const CollapseButton: React.FC<{ style?: React.CSSProperties; }> = ({ collapse, onClick, className, style }) => { return ( - ); diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx index ffec1a90..b42b3aa3 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx @@ -2,9 +2,6 @@ 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 starIcon from "bootstrap-icons/icons/star.svg"; -import bookmarkIcon from "bootstrap-icons/icons/bookmark.svg"; import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; @@ -58,16 +55,14 @@ function TimelineCardTemplate({
{onHighlight != null ? ( - ) : null} {onBookmark != null ? ( - ) : null} diff --git a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx index 408c49a1..233c81bd 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineItem.tsx @@ -2,9 +2,6 @@ import React from "react"; import clsx from "clsx"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import Svg from "react-inlinesvg"; -import chevronDownIcon from "bootstrap-icons/icons/chevron-down.svg"; -import trashIcon from "bootstrap-icons/icons/trash.svg"; import { Modal, Button } from "react-bootstrap"; import { useAvatar } from "@/services/user"; @@ -98,9 +95,8 @@ const TimelineItem: React.FC = (props) => { {props.post.author.nickname} {more != null ? ( - { more.toggle(); e.stopPropagation(); @@ -139,9 +135,8 @@ const TimelineItem: React.FC = (props) => { className="position-absolute position-lt w-100 h-100 mask d-flex justify-content-center align-items-center" onClick={more.toggle} > - { setDeleteDialog(true); e.stopPropagation(); diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx index dfa2f879..207bf6af 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx @@ -1,10 +1,7 @@ import React from "react"; import clsx from "clsx"; import { useTranslation } from "react-i18next"; -import Svg from "react-inlinesvg"; import { Button, Spinner, Row, Col, Form } from "react-bootstrap"; -import textIcon from "bootstrap-icons/icons/card-text.svg"; -import imageIcon from "bootstrap-icons/icons/image.svg"; import { UiLogicError } from "@/common"; @@ -212,10 +209,12 @@ const TimelinePostEdit: React.FC = (props) => { return ( <>
-
-- cgit v1.2.3 From 00d9ff6051ff591ef44cc44d41afd1184297c1c9 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 31 Dec 2020 21:10:17 +0800 Subject: ... --- FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx index b42b3aa3..ece1942f 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx @@ -78,6 +78,7 @@ function TimelineCardTemplate({ } else { return ( Date: Thu, 31 Dec 2020 21:38:22 +0800 Subject: ... --- FrontEnd/src/app/views/home/BoardWithUser.tsx | 10 +++++++ FrontEnd/src/app/views/home/TimelineBoard.tsx | 33 +++++++++++++++------- FrontEnd/src/app/views/home/home.sass | 8 +++++- .../timeline-common/TimelinePageTemplateUI.tsx | 2 ++ 4 files changed, 42 insertions(+), 11 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 469eb388..ab87a58f 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -17,6 +17,16 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { getHttpHighlightClient().list()} + editHandler={ + user.hasHighlightTimelineAdministrationPermission + ? { + onDelete: () => { + // TODO: Implement this. + return Promise.resolve(); + }, + } + : undefined + } /> diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 87ed98e1..0195f1b5 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -30,13 +30,21 @@ const TimelineBoardItem: React.FC = ({ const content = ( <> - {isPersonal ? ( - - ) : ( - - )} - {title} - {name} +
+ {isPersonal ? ( + + ) : ( + + )} + {title} + {name} +
+ {actions != null ? ( +
+ + +
+ ) : null} ); @@ -49,7 +57,7 @@ const TimelineBoardItem: React.FC = ({ ); }; -interface TimelineBoardUIProps { +export interface TimelineBoardUIProps { title?: string; timelines: TimelineInfo[] | "offline" | "loading"; onReload: () => void; @@ -68,8 +76,8 @@ const TimelineBoardUI: React.FC = (props) => { return (
-
- {title != null &&

{title}

} +
+ {title != null &&

{title}

} { editable && (editing ? ( @@ -132,12 +140,16 @@ export interface TimelineBoardProps { title?: string; className?: string; load: () => Promise; + editHandler?: { + onDelete: (timeline: string) => Promise; + }; } const TimelineBoard: React.FC = ({ className, title, load, + editHandler, }) => { const [timelines, setTimelines] = React.useState< TimelineInfo[] | "offline" | "loading" @@ -170,6 +182,7 @@ const TimelineBoard: React.FC = ({ onReload={() => { setTimelines("loading"); }} + editHandler={editHandler} /> ); }; diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index 0c01019b..e15441bc 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -3,13 +3,19 @@ @extend .d-flex @extend .flex-column @extend .py-3 + @extend .px-3 min-height: 200px +.timeline-board-header + display: flex + justify-content: space-between + .timeline-board-item font-size: 1.1em - @extend .px-3 @extend .py-2 transition: background 0.3s + display: flex + justify-content: space-between .icon height: 1.3em color: black diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx index b7cd4a45..20ec6e43 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplateUI.tsx @@ -31,6 +31,7 @@ export interface TimelinePageTemplateUIProps { onManage?: (item: TManageItems | "property") => void; onMember: () => void; onBookmark?: () => void; + onHighlight?: () => void; onPost?: TimelinePostSendCallback; } | I18nText; @@ -157,6 +158,7 @@ export default function TimelinePageTemplateUI( onManage={data.onManage} onMember={data.onMember} onBookmark={data.onBookmark} + onHighlight={data.onHighlight} syncStatus={syncStatus} collapse={cardCollapse} toggleCollapse={toggleCardCollapse} -- cgit v1.2.3 From 0a84c4c86e27e61af4a437f3c173adb220d9f62a Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 31 Dec 2020 22:21:29 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 11 +++++++++-- FrontEnd/src/app/views/home/TimelineBoard.tsx | 25 +++++++++++++++++++++---- FrontEnd/src/app/views/home/home.sass | 5 ++++- 3 files changed, 34 insertions(+), 7 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index 1a833174..f1ccf50b 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -35,10 +35,17 @@ small width: 40px .icon-button - font-size: 1.4em + font-size: 1.4rem cursor: pointer &.large - font-size: 1.6em + font-size: 1.6rem + +.flat-button + cursor: pointer + padding: 0.2em 0.5em + border-radius: 0.2em + &:hover + background-color: $gray-200 .cursor-pointer cursor: pointer diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 0195f1b5..a83095d4 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -14,7 +14,7 @@ interface TimelineBoardItemProps { // If not null, will disable navigation on click. actions?: { onDelete: () => void; - onMove: (e: React.MouseEvent) => void; + onMove: (e: React.PointerEvent) => void; }; } @@ -41,8 +41,8 @@ const TimelineBoardItem: React.FC = ({
{actions != null ? (
- - + +
) : null} @@ -82,6 +82,7 @@ const TimelineBoardUI: React.FC = (props) => { editable && (editing ? (
{ setEditing(false); }} @@ -90,6 +91,7 @@ const TimelineBoardUI: React.FC = (props) => {
) : (
{ setEditing(true); }} @@ -127,7 +129,22 @@ const TimelineBoardUI: React.FC = (props) => { } else { return timelines.map((timeline) => { return ( - + { + //TODO: Implement this. + }, + onMove: () => { + //TODO: Implement this. + }, + } + : undefined + } + /> ); }); } diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index e15441bc..7694b50c 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -3,18 +3,21 @@ @extend .d-flex @extend .flex-column @extend .py-3 - @extend .px-3 min-height: 200px .timeline-board-header + @extend .px-3 display: flex + align-items: center justify-content: space-between .timeline-board-item font-size: 1.1em + @extend .px-3 @extend .py-2 transition: background 0.3s display: flex + align-items: center justify-content: space-between .icon height: 1.3em -- cgit v1.2.3 From 9841031689d6a66a5949d43803faeb251fe471eb Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 00:34:10 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 45 ++++++++++++++++++++++++--- FrontEnd/src/app/views/home/home.sass | 2 +- 2 files changed, 42 insertions(+), 5 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index a83095d4..b389309a 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -11,15 +11,23 @@ import { HttpTimelineInfo } from "@/http/timeline"; interface TimelineBoardItemProps { timeline: HttpTimelineInfo; + // In height. + offset?: number; + // In px. + arbitraryOffset?: number; // If not null, will disable navigation on click. actions?: { onDelete: () => void; - onMove: (e: React.PointerEvent) => void; + onMoveStart: (e: React.PointerEvent) => void; + onMoving: (e: React.PointerEvent) => void; + onMoveEnd: (e: React.PointerEvent) => void; }; } const TimelineBoardItem: React.FC = ({ timeline, + arbitraryOffset, + offset, actions, }) => { const { name, title } = timeline; @@ -42,18 +50,36 @@ const TimelineBoardItem: React.FC = ({ {actions != null ? (
- +
) : null} ); + const offsetStyle: React.CSSProperties = { + translate: + arbitraryOffset != null + ? `0 ${arbitraryOffset}px` + : offset != null + ? `0 ${offset * 100}%` + : undefined, + transition: + arbitraryOffset == null && offset != null ? "translate 0.5s" : undefined, + }; + return actions == null ? ( {content} ) : ( -
{content}
+
+ {content} +
); }; @@ -74,6 +100,11 @@ const TimelineBoardUI: React.FC = (props) => { const [editing, setEditing] = React.useState(false); + const [moveState, setMoveState] = React.useState(null); + return (
@@ -138,7 +169,13 @@ const TimelineBoardUI: React.FC = (props) => { onDelete: () => { //TODO: Implement this. }, - onMove: () => { + onMoveStart: () => { + //TODO: Implement this. + }, + onMoving: () => { + //TODO: Implement this. + }, + onMoveEnd: () => { //TODO: Implement this. }, } diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index 7694b50c..644eb550 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -14,7 +14,7 @@ .timeline-board-item font-size: 1.1em @extend .px-3 - @extend .py-2 + height: 48px transition: background 0.3s display: flex align-items: center -- cgit v1.2.3 From 1c0b8cd0738343095a2e469831265fea1e4e5d26 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 23:16:37 +0800 Subject: ... --- FrontEnd/src/app/views/home/BoardWithUser.tsx | 4 ++ FrontEnd/src/app/views/home/TimelineBoard.tsx | 100 +++++++++++++++++++++++--- FrontEnd/src/app/views/home/home.sass | 1 + 3 files changed, 94 insertions(+), 11 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index ab87a58f..7c736695 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -24,6 +24,10 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { // TODO: Implement this. return Promise.resolve(); }, + onMove: () => { + // TODO: Implement this. + return Promise.resolve(); + }, } : undefined } diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index b389309a..ae4a1180 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -68,8 +68,8 @@ const TimelineBoardItem: React.FC = ({ : offset != null ? `0 ${offset * 100}%` : undefined, - transition: - arbitraryOffset == null && offset != null ? "translate 0.5s" : undefined, + transition: offset != null ? "translate 0.5s" : undefined, + zIndex: arbitraryOffset != null ? 1 : undefined, }; return actions == null ? ( @@ -89,7 +89,8 @@ export interface TimelineBoardUIProps { onReload: () => void; className?: string; editHandler?: { - onDelete: (timeline: string) => Promise; + onMove: (timeline: string, index: number, offset: number) => void; + onDelete: (timeline: string) => void; }; } @@ -103,6 +104,7 @@ const TimelineBoardUI: React.FC = (props) => { const [moveState, setMoveState] = React.useState(null); return ( @@ -158,25 +160,75 @@ const TimelineBoardUI: React.FC = (props) => {
); } else { - return timelines.map((timeline) => { + return timelines.map((timeline, index) => { + const height = 48; + + let offset: number | undefined = undefined; + let arbitraryOffset: number | undefined = undefined; + if (moveState != null) { + if (index === moveState.index) { + arbitraryOffset = moveState.offset; + } else { + if (moveState.offset >= 0) { + const offsetCount = Math.round(moveState.offset / height); + if ( + index > moveState.index && + index <= moveState.index + offsetCount + ) { + offset = -1; + } + } else { + const offsetCount = Math.round(-moveState.offset / height); + if ( + index < moveState.index && + index >= moveState.index - offsetCount + ) { + offset = 1; + } + } + } + } + return ( { - //TODO: Implement this. + editHandler.onDelete(timeline.name); }, - onMoveStart: () => { - //TODO: Implement this. + onMoveStart: (e) => { + if (moveState != null) return; + setMoveState({ + index, + offset: 0, + startPointY: e.clientY, + }); }, - onMoving: () => { - //TODO: Implement this. + onMoving: (e) => { + if (moveState == null) return; + setMoveState({ + index, + offset: e.clientY - moveState.startPointY, + startPointY: moveState.startPointY, + }); }, onMoveEnd: () => { - //TODO: Implement this. + if (moveState != null) { + const offsetCount = Math.round( + moveState.offset / height + ); + editHandler.onMove( + timeline.name, + moveState.index, + offsetCount + ); + } + setMoveState(null); }, } : undefined @@ -195,6 +247,7 @@ export interface TimelineBoardProps { className?: string; load: () => Promise; editHandler?: { + onMove: (timeline: string, index: number, offset: number) => Promise; onDelete: (timeline: string) => Promise; }; } @@ -236,7 +289,32 @@ const TimelineBoard: React.FC = ({ onReload={() => { setTimelines("loading"); }} - editHandler={editHandler} + editHandler={ + typeof timelines === "object" && editHandler != null + ? { + onMove: (timeline, index, offset) => { + const newTimelines = timelines.slice(); + const [t] = newTimelines.splice(index, 1); + newTimelines.splice(index + offset, 0, t); + setTimelines(newTimelines); + editHandler.onMove(timeline, index, offset).then(null, () => { + setTimelines(timelines); + }); + }, + onDelete: (timeline) => { + const newTimelines = timelines.slice(); + newTimelines.splice( + timelines.findIndex((t) => t.name === timeline), + 1 + ); + setTimelines(newTimelines); + editHandler.onDelete(timeline).then(null, () => { + setTimelines(timelines); + }); + }, + } + : undefined + } /> ); }; diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index 644eb550..e2dc4b66 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -4,6 +4,7 @@ @extend .flex-column @extend .py-3 min-height: 200px + position: relative .timeline-board-header @extend .px-3 -- cgit v1.2.3 From 7a386c43c95e9baea05c798b4cf1c32f8a72ac01 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 23:20:21 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineCreateDialog.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx b/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx index 12bbfb54..5dcba612 100644 --- a/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx +++ b/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx @@ -1,7 +1,11 @@ import React from "react"; import { useHistory } from "react-router"; -import { validateTimelineName, timelineService } from "@/services/timeline"; +import { + validateTimelineName, + timelineService, + TimelineInfo, +} from "@/services/timeline"; import OperationDialog from "../common/OperationDialog"; interface TimelineCreateDialogProps { @@ -12,8 +16,6 @@ interface TimelineCreateDialogProps { const TimelineCreateDialog: React.FC = (props) => { const history = useHistory(); - let nameSaved: string; - return ( = (props) => { return null; } }} - onProcess={([name]) => { + onProcess={([name]): Promise => { return timelineService.createTimeline(name).toPromise(); }} - onSuccessAndClose={() => { - history.push(`timelines/${nameSaved}`); + onSuccessAndClose={(timeline: TimelineInfo) => { + history.push(`timelines/${timeline.name}`); }} failurePrompt={(e) => `${e as string}`} /> -- cgit v1.2.3 From 54703fd1e76718468c68fe3c031a80f9847679c7 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 23:23:18 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index ae4a1180..77801a57 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -52,8 +52,14 @@ const TimelineBoardItem: React.FC = ({ { + e.currentTarget.setPointerCapture(e.pointerId); + actions.onMoveStart(e); + }} + onPointerUp={(e) => { + actions.onMoveEnd(e); + e.currentTarget.releasePointerCapture(e.pointerId); + }} onPointerMove={actions.onMoving} />
@@ -176,6 +182,8 @@ const TimelineBoardUI: React.FC = (props) => { index <= moveState.index + offsetCount ) { offset = -1; + } else { + offset = 0; } } else { const offsetCount = Math.round(-moveState.offset / height); @@ -184,6 +192,8 @@ const TimelineBoardUI: React.FC = (props) => { index >= moveState.index - offsetCount ) { offset = 1; + } else { + offset = 0; } } } -- cgit v1.2.3 From 08b5335d7329efb8dc0a7761362d826d53126ac7 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 23:37:59 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 25 ++++++++++++++----------- FrontEnd/src/app/views/home/home.sass | 9 ++++++++- 2 files changed, 22 insertions(+), 12 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 77801a57..93da14cb 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -38,17 +38,16 @@ const TimelineBoardItem: React.FC = ({ const content = ( <> -
- {isPersonal ? ( - - ) : ( - - )} - {title} - {name} -
+ {isPersonal ? ( + + ) : ( + + )} + {title} + {name} + {actions != null ? ( -
+
= ({ }} onPointerUp={(e) => { actions.onMoveEnd(e); - e.currentTarget.releasePointerCapture(e.pointerId); + try { + e.currentTarget.releasePointerCapture(e.pointerId); + } catch (_) { + void null; + } }} onPointerMove={actions.onMoving} /> diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index e2dc4b66..6af1b6d8 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -19,10 +19,17 @@ transition: background 0.3s display: flex align-items: center - justify-content: space-between .icon height: 1.3em color: black @extend .mr-2 &:hover background: $gray-300 + .right + display: flex + align-items: center + flex-shrink: 0 + .title + white-space: nowrap + overflow: hidden + text-overflow: ellipsis -- cgit v1.2.3 From 14b951ae77c294ff7e78b6601ab4208bc2a48f23 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 1 Jan 2021 23:47:04 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 3 --- 1 file changed, 3 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index f1ccf50b..4c57030d 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -13,9 +13,6 @@ @import './views/admin/admin' -body - margin: 0 - small line-height: 1.2 -- cgit v1.2.3 From e4950c59e33f81167598b21e69f9b13d352edd36 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 18:27:20 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 219 +++++++++++++++----------- 1 file changed, 125 insertions(+), 94 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 93da14cb..120083e3 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -18,9 +18,11 @@ interface TimelineBoardItemProps { // If not null, will disable navigation on click. actions?: { onDelete: () => void; - onMoveStart: (e: React.PointerEvent) => void; - onMoving: (e: React.PointerEvent) => void; - onMoveEnd: (e: React.PointerEvent) => void; + onMove: { + start: (e: React.PointerEvent) => void; + moving: (e: React.PointerEvent) => void; + end: (e: React.PointerEvent) => void; + }; }; } @@ -48,22 +50,25 @@ const TimelineBoardItem: React.FC = ({ {actions != null ? (
- + { e.currentTarget.setPointerCapture(e.pointerId); - actions.onMoveStart(e); + actions.onMove.start(e); }} onPointerUp={(e) => { - actions.onMoveEnd(e); + actions.onMove.end(e); try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) { void null; } }} - onPointerMove={actions.onMoving} + onPointerMove={actions.onMove.moving} />
) : null} @@ -92,7 +97,113 @@ const TimelineBoardItem: React.FC = ({ ); }; -export interface TimelineBoardUIProps { +interface TimelineBoardItemContainerProps { + timelines: TimelineInfo[]; + editHandler?: { + onMove: (timeline: string, index: number, offset: number) => void; + onDelete: (timeline: string) => void; + }; +} + +const TimelineBoardItemContainer: React.FC = ({ + timelines, + editHandler, +}) => { + const [moveState, setMoveState] = React.useState(null); + + return ( + <> + {timelines.map((timeline, index) => { + const height = 48; + + let offset: number | undefined = undefined; + let arbitraryOffset: number | undefined = undefined; + if (moveState != null) { + if (index === moveState.index) { + arbitraryOffset = moveState.offset; + } else { + if (moveState.offset >= 0) { + const offsetCount = Math.round(moveState.offset / height); + if ( + index > moveState.index && + index <= moveState.index + offsetCount + ) { + offset = -1; + } else { + offset = 0; + } + } else { + const offsetCount = Math.round(-moveState.offset / height); + if ( + index < moveState.index && + index >= moveState.index - offsetCount + ) { + offset = 1; + } else { + offset = 0; + } + } + } + } + + return ( + { + editHandler.onDelete(timeline.name); + }, + onMove: { + start: (e) => { + if (moveState != null) return; + setMoveState({ + index, + offset: 0, + startPointY: e.clientY, + }); + }, + moving: (e) => { + if (moveState == null) return; + setMoveState({ + index, + offset: e.clientY - moveState.startPointY, + startPointY: moveState.startPointY, + }); + }, + end: () => { + if (moveState != null) { + const offsetCount = Math.round( + moveState.offset / height + ); + editHandler.onMove( + timeline.name, + moveState.index, + offsetCount + ); + } + setMoveState(null); + }, + }, + } + : undefined + } + /> + ); + })} + + ); +}; + +interface TimelineBoardUIProps { title?: string; timelines: TimelineInfo[] | "offline" | "loading"; onReload: () => void; @@ -110,12 +221,6 @@ const TimelineBoardUI: React.FC = (props) => { const [editing, setEditing] = React.useState(false); - const [moveState, setMoveState] = React.useState(null); - return (
@@ -169,86 +274,12 @@ const TimelineBoardUI: React.FC = (props) => {
); } else { - return timelines.map((timeline, index) => { - const height = 48; - - let offset: number | undefined = undefined; - let arbitraryOffset: number | undefined = undefined; - if (moveState != null) { - if (index === moveState.index) { - arbitraryOffset = moveState.offset; - } else { - if (moveState.offset >= 0) { - const offsetCount = Math.round(moveState.offset / height); - if ( - index > moveState.index && - index <= moveState.index + offsetCount - ) { - offset = -1; - } else { - offset = 0; - } - } else { - const offsetCount = Math.round(-moveState.offset / height); - if ( - index < moveState.index && - index >= moveState.index - offsetCount - ) { - offset = 1; - } else { - offset = 0; - } - } - } - } - - return ( - { - editHandler.onDelete(timeline.name); - }, - onMoveStart: (e) => { - if (moveState != null) return; - setMoveState({ - index, - offset: 0, - startPointY: e.clientY, - }); - }, - onMoving: (e) => { - if (moveState == null) return; - setMoveState({ - index, - offset: e.clientY - moveState.startPointY, - startPointY: moveState.startPointY, - }); - }, - onMoveEnd: () => { - if (moveState != null) { - const offsetCount = Math.round( - moveState.offset / height - ); - editHandler.onMove( - timeline.name, - moveState.index, - offsetCount - ); - } - setMoveState(null); - }, - } - : undefined - } - /> - ); - }); + return ( + + ); } })()}
-- cgit v1.2.3 From 723d7b038ad5b3f391c0d6f400f3c12fabf75667 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 18:49:32 +0800 Subject: ... --- FrontEnd/src/app/views/home/TimelineBoard.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 120083e3..bb3f5947 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -100,6 +100,7 @@ const TimelineBoardItem: React.FC = ({ interface TimelineBoardItemContainerProps { timelines: TimelineInfo[]; editHandler?: { + // offset may exceed index range plusing index. onMove: (timeline: string, index: number, offset: number) => void; onDelete: (timeline: string) => void; }; @@ -277,7 +278,21 @@ const TimelineBoardUI: React.FC = (props) => { return ( { + if (index + offset >= timelines.length) { + offset = timelines.length - index - 1; + } else if (index + offset < 0) { + offset = -index; + } + editHandler.onMove(timeline, index, offset); + }, + } + : undefined + } /> ); } -- cgit v1.2.3 From 955704d1e8c3f47b8672cbbd77f4bfb6068fd7b2 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 19:01:32 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 3 + FrontEnd/src/app/locales/zh/translation.json | 3 + FrontEnd/src/app/views/home/BoardWithUser.tsx | 89 ++++++++++++++++++--------- FrontEnd/src/app/views/home/TimelineBoard.tsx | 46 +++++++------- FrontEnd/src/app/views/home/home.sass | 1 + 5 files changed, 89 insertions(+), 53 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index be80d21e..28e7c978 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -1,6 +1,8 @@ { "welcome": "Welcome!", "search": "Search", + "edit": "Edit", + "done": "Done", "loadFailReload": "Load failed, click <1>here to reload.", "serviceWorker": { "availableOffline": "Timeline is now cached in your computer and you can use it offline. 🎉🎉🎉", @@ -23,6 +25,7 @@ "highlightTimeline": "Highlight Timelines", "relatedTimeline": "Timelines Related To You", "publicTimeline": "Public Timelines", + "bookmarkTimeline": "Bookmark Timelines", "offlinePrompt": "Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here to refresh.", "createButton": "Create Timeline", "createDialog": { diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 8c925eb9..708d0b3b 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -1,6 +1,8 @@ { "welcome": "欢迎!", "search": "搜索", + "edit": "编辑", + "done": "完成", "loadFailReload": "加载失败,<1>点击重试。", "serviceWorker": { "availableOffline": "Timeline 已经缓存在本地,你可以离线使用它。🎉🎉🎉", @@ -23,6 +25,7 @@ "highlightTimeline": "高光时间线", "relatedTimeline": "关于你的时间线", "publicTimeline": "公开时间线", + "bookmarkTimeline": "书签时间线", "offlinePrompt": "你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。", "createButton": "创建时间线", "createDialog": { diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 7c736695..1c6f713a 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { AuthUser } from "@/services/user"; import { getHttpHighlightClient } from "@/http/highlight"; import { getHttpTimelineClient } from "@/http/timeline"; +import { getHttpBookmarkClient } from "@/http/bookmark"; import TimelineBoard from "./TimelineBoard"; @@ -12,36 +13,64 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { const { t } = useTranslation(); return ( - - - getHttpHighlightClient().list()} - editHandler={ - user.hasHighlightTimelineAdministrationPermission - ? { - onDelete: () => { - // TODO: Implement this. - return Promise.resolve(); - }, - onMove: () => { - // TODO: Implement this. - return Promise.resolve(); - }, - } - : undefined - } - /> - - - - getHttpTimelineClient().listTimeline({ relate: user.username }) - } - /> - - + <> + + + getHttpBookmarkClient().list(user.token)} + editHandler={{ + onDelete: () => { + // TODO: Implement this. + return Promise.resolve(); + }, + onMove: () => { + // TODO: Implement this. + return Promise.resolve(); + }, + }} + /> + + + + getHttpTimelineClient().listTimeline({ relate: user.username }) + } + /> + + + + + getHttpHighlightClient().list()} + editHandler={ + user.hasHighlightTimelineAdministrationPermission + ? { + onDelete: () => { + // TODO: Implement this. + return Promise.resolve(); + }, + onMove: () => { + // TODO: Implement this. + return Promise.resolve(); + }, + } + : undefined + } + /> + + + + getHttpTimelineClient().listTimeline({ visibility: "Public" }) + } + /> + + + ); }; diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index bb3f5947..083f4034 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -1,7 +1,7 @@ import React from "react"; import clsx from "clsx"; import { Link } from "react-router-dom"; -import { Trans } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { Spinner } from "react-bootstrap"; import { TimelineInfo } from "@/services/timeline"; @@ -218,6 +218,8 @@ interface TimelineBoardUIProps { const TimelineBoardUI: React.FC = (props) => { const { title, timelines, className, editHandler } = props; + const { t } = useTranslation(); + const editable = editHandler != null; const [editing, setEditing] = React.useState(false); @@ -226,28 +228,26 @@ const TimelineBoardUI: React.FC = (props) => {
{title != null &&

{title}

} - { - editable && - (editing ? ( -
{ - setEditing(false); - }} - > - Done -
- ) : ( -
{ - setEditing(true); - }} - > - Edit -
- )) // TODO: i18n - } + {editable && + (editing ? ( +
{ + setEditing(false); + }} + > + {t("done")} +
+ ) : ( +
{ + setEditing(true); + }} + > + {t("edit")} +
+ ))}
{(() => { if (timelines === "loading") { diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass index 6af1b6d8..4b86f241 100644 --- a/FrontEnd/src/app/views/home/home.sass +++ b/FrontEnd/src/app/views/home/home.sass @@ -4,6 +4,7 @@ @extend .flex-column @extend .py-3 min-height: 200px + height: 100% position: relative .timeline-board-header -- cgit v1.2.3 From 2037b9ca65ab17e60d5b0c4788a754a580fd54b6 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 19:13:47 +0800 Subject: ... --- FrontEnd/src/app/index.sass | 3 +++ FrontEnd/src/app/views/home/TimelineBoard.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/index.sass b/FrontEnd/src/app/index.sass index 4c57030d..87616998 100644 --- a/FrontEnd/src/app/index.sass +++ b/FrontEnd/src/app/index.sass @@ -85,3 +85,6 @@ textarea color: $value &:hover color: adjust-color($value, $lightness: +15%) + +.touch-action-none + touch-action: none diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx index 083f4034..c3f01aed 100644 --- a/FrontEnd/src/app/views/home/TimelineBoard.tsx +++ b/FrontEnd/src/app/views/home/TimelineBoard.tsx @@ -55,7 +55,7 @@ const TimelineBoardItem: React.FC = ({ onClick={actions.onDelete} /> { e.currentTarget.setPointerCapture(e.pointerId); actions.onMove.start(e); -- cgit v1.2.3 From a1e912b473b41185cdf0e6bd1810a2becb8f9659 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 19:31:11 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 6 +++ FrontEnd/src/app/locales/zh/translation.json | 6 +++ FrontEnd/src/app/views/home/BoardWithUser.tsx | 72 ++++++++++++++++++++++----- 3 files changed, 72 insertions(+), 12 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index 28e7c978..414cc747 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -27,6 +27,12 @@ "publicTimeline": "Public Timelines", "bookmarkTimeline": "Bookmark Timelines", "offlinePrompt": "Oh oh, it seems you are offline. Here list some timelines cached locally. You can view them or click <1>here to refresh.", + "message": { + "moveHighlightFail": "Failed to move highlight timeline.", + "deleteHighlightFail": "Failed to delete highlight timeline.", + "moveBookmarkFail": "Failed to move bookmark timeline.", + "deleteBookmarkFail": "Failed to delete bookmark timeline." + }, "createButton": "Create Timeline", "createDialog": { "title": "Create Timeline!", diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 708d0b3b..bbee28af 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -27,6 +27,12 @@ "publicTimeline": "公开时间线", "bookmarkTimeline": "书签时间线", "offlinePrompt": "你好像处于离线状态。以下是一些缓存在本地的时间线。你可以查看它们或者<1>点击重新获取在线信息。", + "message": { + "moveHighlightFail": "移动高光时间线失败。", + "deleteHighlightFail": "删除高光时间线失败。", + "moveBookmarkFail": "移动书签时间线失败。", + "deleteBookmarkFail": "删除书签时间线失败。" + }, "createButton": "创建时间线", "createDialog": { "title": "创建时间线!", diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 1c6f713a..8afe440b 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -3,6 +3,8 @@ import { Row, Col } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { AuthUser } from "@/services/user"; +import { pushAlert } from "@/services/alert"; + import { getHttpHighlightClient } from "@/http/highlight"; import { getHttpTimelineClient } from "@/http/timeline"; import { getHttpBookmarkClient } from "@/http/bookmark"; @@ -20,13 +22,36 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { title={t("home.bookmarkTimeline")} load={() => getHttpBookmarkClient().list(user.token)} editHandler={{ - onDelete: () => { - // TODO: Implement this. - return Promise.resolve(); + onDelete: (timeline) => { + return getHttpBookmarkClient() + .delete(timeline, user.token) + .catch((e) => { + pushAlert({ + message: { + type: "i18n", + key: "home.message.deleteBookmarkFail", + }, + type: "danger", + }); + throw e; + }); }, - onMove: () => { - // TODO: Implement this. - return Promise.resolve(); + onMove: (timeline, index, offset) => { + return getHttpBookmarkClient() + .move( + { timeline, newPosition: index + offset + 1 }, // +1 because backend contract: index starts at 1 + user.token + ) + .catch((e) => { + pushAlert({ + message: { + type: "i18n", + key: "home.message.moveBookmarkFail", + }, + type: "danger", + }); + throw e; + }); }, }} /> @@ -48,13 +73,36 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { editHandler={ user.hasHighlightTimelineAdministrationPermission ? { - onDelete: () => { - // TODO: Implement this. - return Promise.resolve(); + onDelete: (timeline) => { + return getHttpHighlightClient() + .delete(timeline, user.token) + .catch((e) => { + pushAlert({ + message: { + type: "i18n", + key: "home.message.deleteHighlightFail", + }, + type: "danger", + }); + throw e; + }); }, - onMove: () => { - // TODO: Implement this. - return Promise.resolve(); + onMove: (timeline, index, offset) => { + return getHttpHighlightClient() + .move( + { timeline, newPosition: index + offset + 1 }, // +1 because backend contract: index starts at 1 + user.token + ) + .catch((e) => { + pushAlert({ + message: { + type: "i18n", + key: "home.message.moveHighlightFail", + }, + type: "danger", + }); + throw e; + }); }, } : undefined -- cgit v1.2.3 From 8af803cb0da57af1355ad28cd056cb5dcf6d6915 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 3 Jan 2021 19:35:36 +0800 Subject: ... --- FrontEnd/src/app/locales/en/translation.json | 4 +++- FrontEnd/src/app/locales/zh/translation.json | 4 +++- .../src/app/views/timeline-common/TimelinePageTemplate.tsx | 10 ++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) (limited to 'FrontEnd/src') diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index 414cc747..596b5217 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -106,7 +106,9 @@ "title": "Confirm Delete", "prompt": "Are you sure to delete the post? This operation is not recoverable." } - } + }, + "addHighlightSuccess": "Succeeded to add highlight.", + "addBookmarkSuccess": "Succeeded to add bookmark." }, "user": { "username": "username", diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index bbee28af..e15e177e 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -106,7 +106,9 @@ "title": "确认删除", "prompt": "确定删除这个消息?这个操作不可撤销。" } - } + }, + "addHighlightSuccess": "成功添加高光。", + "addBookmarkSuccess": "成功添加书签。" }, "user": { "username": "用户名", diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 21720601..7f5c8206 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -125,7 +125,10 @@ export default function TimelinePageTemplate( .put(name, user.token) .then(() => { pushAlert({ - message: "Succeeded to add bookmark!", // TODO: i18n + message: { + type: "i18n", + key: "timeline.addBookmarkSuccess", + }, type: "success", }); }); @@ -138,7 +141,10 @@ export default function TimelinePageTemplate( .put(name, user.token) .then(() => { pushAlert({ - message: "Succeeded to add highlight!", // TODO: i18n + message: { + type: "i18n", + key: "timeline.addHighlightSuccess", + }, type: "success", }); }); -- cgit v1.2.3