aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views/home
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views/home')
-rw-r--r--FrontEnd/src/app/views/home/BoardWithUser.tsx111
-rw-r--r--FrontEnd/src/app/views/home/BoardWithoutUser.tsx33
-rw-r--r--FrontEnd/src/app/views/home/TimelineBoard.tsx370
-rw-r--r--FrontEnd/src/app/views/home/TimelineCreateDialog.tsx53
-rw-r--r--FrontEnd/src/app/views/home/TimelineListView.tsx101
-rw-r--r--FrontEnd/src/app/views/home/WebsiteIntroduction.tsx69
-rw-r--r--FrontEnd/src/app/views/home/home.sass57
-rw-r--r--FrontEnd/src/app/views/home/index.tsx79
8 files changed, 251 insertions, 622 deletions
diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx
deleted file mode 100644
index 3263c745..00000000
--- a/FrontEnd/src/app/views/home/BoardWithUser.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React from "react";
-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";
-
-import TimelineBoard from "./TimelineBoard";
-
-const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => {
- const { t } = useTranslation();
-
- return (
- <>
- <Row className="my-3 justify-content-center">
- <Col xs="12" md="6">
- <TimelineBoard
- title={t("home.bookmarkTimeline")}
- load={() => getHttpBookmarkClient().list()}
- editHandler={{
- onDelete: (timeline) => {
- return getHttpBookmarkClient()
- .delete(timeline)
- .catch((e) => {
- pushAlert({
- message: "home.message.deleteBookmarkFail",
- type: "danger",
- });
- throw e;
- });
- },
- onMove: (timeline, index, offset) => {
- return getHttpBookmarkClient()
- .move(
- { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1
- )
- .catch((e) => {
- pushAlert({
- message: "home.message.moveBookmarkFail",
- type: "danger",
- });
- throw e;
- });
- },
- }}
- />
- </Col>
- <Col xs="12" md="6" className="my-3 my-md-0">
- <TimelineBoard
- title={t("home.relatedTimeline")}
- load={() =>
- getHttpTimelineClient().listTimeline({ relate: user.username })
- }
- />
- </Col>
- </Row>
- <Row className="my-3 justify-content-center">
- <Col xs="12" md="6">
- <TimelineBoard
- title={t("home.highlightTimeline")}
- load={() => getHttpHighlightClient().list()}
- editHandler={
- user.hasHighlightTimelineAdministrationPermission
- ? {
- onDelete: (timeline) => {
- return getHttpHighlightClient()
- .delete(timeline)
- .catch((e) => {
- pushAlert({
- message: "home.message.deleteHighlightFail",
- type: "danger",
- });
- throw e;
- });
- },
- onMove: (timeline, index, offset) => {
- return getHttpHighlightClient()
- .move(
- { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1
- )
- .catch((e) => {
- pushAlert({
- message: "home.message.moveHighlightFail",
- type: "danger",
- });
- throw e;
- });
- },
- }
- : undefined
- }
- />
- </Col>
- <Col xs="12" md="6" className="my-3 my-md-0">
- <TimelineBoard
- title={t("home.publicTimeline")}
- load={() =>
- getHttpTimelineClient().listTimeline({ visibility: "Public" })
- }
- />
- </Col>
- </Row>
- </>
- );
-};
-
-export default BoardWithUser;
diff --git a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx b/FrontEnd/src/app/views/home/BoardWithoutUser.tsx
deleted file mode 100644
index d9c7fcf4..00000000
--- a/FrontEnd/src/app/views/home/BoardWithoutUser.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from "react";
-import { Row, Col } from "react-bootstrap";
-import { useTranslation } from "react-i18next";
-
-import { getHttpHighlightClient } from "@/http/highlight";
-import { getHttpTimelineClient } from "@/http/timeline";
-
-import TimelineBoard from "./TimelineBoard";
-
-const BoardWithoutUser: React.FC = () => {
- const { t } = useTranslation();
-
- return (
- <Row className="my-3 justify-content-center">
- <Col xs="12" md="6">
- <TimelineBoard
- title={t("home.highlightTimeline")}
- load={() => getHttpHighlightClient().list()}
- />
- </Col>
- <Col xs="12" md="6" className="my-3 my-md-0">
- <TimelineBoard
- title={t("home.publicTimeline")}
- load={() =>
- getHttpTimelineClient().listTimeline({ visibility: "Public" })
- }
- />
- </Col>
- </Row>
- );
-};
-
-export default BoardWithoutUser;
diff --git a/FrontEnd/src/app/views/home/TimelineBoard.tsx b/FrontEnd/src/app/views/home/TimelineBoard.tsx
deleted file mode 100644
index e0511422..00000000
--- a/FrontEnd/src/app/views/home/TimelineBoard.tsx
+++ /dev/null
@@ -1,370 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { Spinner } from "react-bootstrap";
-
-import { HttpTimelineInfo } from "@/http/timeline";
-
-import TimelineLogo from "../common/TimelineLogo";
-import UserTimelineLogo from "../common/UserTimelineLogo";
-import LoadFailReload from "../common/LoadFailReload";
-
-interface TimelineBoardItemProps {
- timeline: HttpTimelineInfo;
- // In height.
- offset?: number;
- // In px.
- arbitraryOffset?: number;
- // If not null, will disable navigation on click.
- actions?: {
- onDelete: () => void;
- onMove: {
- start: (e: React.PointerEvent) => void;
- moving: (e: React.PointerEvent) => void;
- end: (e: React.PointerEvent) => void;
- };
- };
-}
-
-const TimelineBoardItem: React.FC<TimelineBoardItemProps> = ({
- timeline,
- arbitraryOffset,
- offset,
- actions,
-}) => {
- const { name, title } = timeline;
- const isPersonal = name.startsWith("@");
- const url = isPersonal
- ? `/users/${timeline.owner.username}`
- : `/timelines/${name}`;
-
- const content = (
- <>
- {isPersonal ? (
- <UserTimelineLogo className="icon" />
- ) : (
- <TimelineLogo className="icon" />
- )}
- <span className="title">{title}</span>
- <small className="ml-2 text-secondary">{name}</small>
- <span className="flex-grow-1"></span>
- {actions != null ? (
- <div className="right">
- <i
- className="bi-trash icon-button text-danger px-2"
- onClick={actions.onDelete}
- />
- <i
- className="bi-grip-vertical icon-button text-gray px-2 touch-action-none"
- onPointerDown={(e) => {
- e.currentTarget.setPointerCapture(e.pointerId);
- actions.onMove.start(e);
- }}
- onPointerUp={(e) => {
- actions.onMove.end(e);
- try {
- e.currentTarget.releasePointerCapture(e.pointerId);
- } catch (_) {
- void null;
- }
- }}
- onPointerMove={actions.onMove.moving}
- />
- </div>
- ) : null}
- </>
- );
-
- const offsetStyle: React.CSSProperties = {
- transform:
- arbitraryOffset != null
- ? `translate(0,${arbitraryOffset}px)`
- : offset != null
- ? `translate(0,${offset * 100}%)`
- : undefined,
- transition: offset != null ? "transform 0.5s" : undefined,
- zIndex: arbitraryOffset != null ? 1 : undefined,
- };
-
- return actions == null ? (
- <Link to={url} className="timeline-board-item">
- {content}
- </Link>
- ) : (
- <div style={offsetStyle} className="timeline-board-item">
- {content}
- </div>
- );
-};
-
-interface TimelineBoardItemContainerProps {
- timelines: HttpTimelineInfo[];
- editHandler?: {
- // offset may exceed index range plusing index.
- onMove: (timeline: string, index: number, offset: number) => void;
- onDelete: (timeline: string) => void;
- };
-}
-
-const TimelineBoardItemContainer: React.FC<TimelineBoardItemContainerProps> = ({
- timelines,
- editHandler,
-}) => {
- const [moveState, setMoveState] = React.useState<null | {
- index: number;
- offset: number;
- startPointY: number;
- }>(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 (
- <TimelineBoardItem
- key={timeline.name}
- timeline={timeline}
- offset={offset}
- arbitraryOffset={arbitraryOffset}
- actions={
- editHandler != null
- ? {
- onDelete: () => {
- 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: HttpTimelineInfo[] | "offline" | "loading";
- onReload: () => void;
- className?: string;
- editHandler?: {
- onMove: (timeline: string, index: number, offset: number) => void;
- onDelete: (timeline: string) => void;
- };
-}
-
-const TimelineBoardUI: React.FC<TimelineBoardUIProps> = (props) => {
- const { title, timelines, className, editHandler } = props;
-
- const { t } = useTranslation();
-
- const editable = editHandler != null;
-
- const [editing, setEditing] = React.useState<boolean>(false);
-
- return (
- <div className={classnames("timeline-board", className)}>
- <div className="timeline-board-header">
- {title != null && <h3>{title}</h3>}
- {editable &&
- (editing ? (
- <div
- className="flat-button text-primary"
- onClick={() => {
- setEditing(false);
- }}
- >
- {t("done")}
- </div>
- ) : (
- <div
- className="flat-button text-primary"
- onClick={() => {
- setEditing(true);
- }}
- >
- {t("edit")}
- </div>
- ))}
- </div>
- {(() => {
- if (timelines === "loading") {
- return (
- <div className="d-flex flex-grow-1 justify-content-center align-items-center">
- <Spinner variant="primary" animation="border" />
- </div>
- );
- } else if (timelines === "offline") {
- return (
- <div className="d-flex flex-grow-1 justify-content-center align-items-center">
- <LoadFailReload onReload={props.onReload} />
- </div>
- );
- } else {
- return (
- <TimelineBoardItemContainer
- timelines={timelines}
- editHandler={
- editHandler && editing
- ? {
- onDelete: editHandler.onDelete,
- onMove: (timeline, index, offset) => {
- if (index + offset >= timelines.length) {
- offset = timelines.length - index - 1;
- } else if (index + offset < 0) {
- offset = -index;
- }
- editHandler.onMove(timeline, index, offset);
- },
- }
- : undefined
- }
- />
- );
- }
- })()}
- </div>
- );
-};
-
-export interface TimelineBoardProps {
- title?: string;
- className?: string;
- load: () => Promise<HttpTimelineInfo[]>;
- editHandler?: {
- onMove: (timeline: string, index: number, offset: number) => Promise<void>;
- onDelete: (timeline: string) => Promise<void>;
- };
-}
-
-const TimelineBoard: React.FC<TimelineBoardProps> = ({
- className,
- title,
- load,
- editHandler,
-}) => {
- const [timelines, setTimelines] = React.useState<
- HttpTimelineInfo[] | "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 (
- <TimelineBoardUI
- title={title}
- className={className}
- timelines={timelines}
- onReload={() => {
- setTimelines("loading");
- }}
- 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
- }
- />
- );
-};
-
-export default TimelineBoard;
diff --git a/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx b/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx
deleted file mode 100644
index b4e25ba1..00000000
--- a/FrontEnd/src/app/views/home/TimelineCreateDialog.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from "react";
-import { useHistory } from "react-router";
-
-import { validateTimelineName } from "@/services/timeline";
-import OperationDialog from "../common/OperationDialog";
-import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
-
-interface TimelineCreateDialogProps {
- open: boolean;
- close: () => void;
-}
-
-const TimelineCreateDialog: React.FC<TimelineCreateDialogProps> = (props) => {
- const history = useHistory();
-
- return (
- <OperationDialog
- open={props.open}
- close={props.close}
- themeColor="success"
- title="home.createDialog.title"
- inputScheme={
- [
- {
- type: "text",
- label: "home.createDialog.name",
- helperText: "home.createDialog.nameFormat",
- },
- ] as const
- }
- inputValidator={([name]) => {
- if (name.length === 0) {
- return { 0: "home.createDialog.noEmpty" };
- } else if (name.length > 26) {
- return { 0: "home.createDialog.tooLong" };
- } else if (!validateTimelineName(name)) {
- return { 0: "home.createDialog.badFormat" };
- } else {
- return null;
- }
- }}
- onProcess={([name]): Promise<HttpTimelineInfo> =>
- getHttpTimelineClient().postTimeline({ name })
- }
- onSuccessAndClose={(timeline: HttpTimelineInfo) => {
- history.push(`timelines/${timeline.name}`);
- }}
- failurePrompt={(e) => `${e as string}`}
- />
- );
-};
-
-export default TimelineCreateDialog;
diff --git a/FrontEnd/src/app/views/home/TimelineListView.tsx b/FrontEnd/src/app/views/home/TimelineListView.tsx
new file mode 100644
index 00000000..9c44a0c2
--- /dev/null
+++ b/FrontEnd/src/app/views/home/TimelineListView.tsx
@@ -0,0 +1,101 @@
+import React from "react";
+
+import { convertI18nText, I18nText } from "@/common";
+
+import { HttpTimelineInfo } from "@/http/timeline";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
+
+interface TimelineListItemProps {
+ timeline: HttpTimelineInfo;
+}
+
+const TimelineListItem: React.FC<TimelineListItemProps> = ({ timeline }) => {
+ const url = React.useMemo(
+ () =>
+ timeline.name.startsWith("@")
+ ? `/users/${timeline.owner.username}`
+ : `/timelines/${timeline.name}`,
+ [timeline]
+ );
+
+ return (
+ <div className="home-v2-timeline-list-item home-v2-timeline-list-item-timeline">
+ <svg className="home-v2-timeline-list-item-line" viewBox="0 0 120 100">
+ <path
+ d="M 80,50 m 0,-12 a 12 12 180 1 1 0,24 12 12 180 1 1 0,-24 z M 60,0 h 40 v 100 h -40 z"
+ fillRule="evenodd"
+ fill="#007bff"
+ />
+ </svg>
+ <div>
+ <div>{timeline.title}</div>
+ <div>
+ <small className="text-secondary">{timeline.description}</small>
+ </div>
+ </div>
+ <Link to={url}>
+ <i className="icon-button bi-arrow-right ml-3" />
+ </Link>
+ </div>
+ );
+};
+
+const TimelineListArrow: React.FC = () => {
+ return (
+ <div>
+ <div className="home-v2-timeline-list-item">
+ <svg className="home-v2-timeline-list-item-line" viewBox="0 0 120 60">
+ <path d="M 60,0 h 40 v 20 l -20,20 l -20,-20 z" fill="#007bff" />
+ </svg>
+ </div>
+ <div className="home-v2-timeline-list-item">
+ <svg
+ className="home-v2-timeline-list-item-line home-v2-timeline-list-loading-head"
+ viewBox="0 0 120 40"
+ >
+ <path
+ d="M 60,10 l 20,20 l 20,-20"
+ fill="none"
+ stroke="#007bff"
+ strokeWidth="5"
+ />
+ </svg>
+ </div>
+ </div>
+ );
+};
+
+interface TimelineListViewProps {
+ headerText?: I18nText;
+ timelines?: HttpTimelineInfo[];
+}
+
+const TimelineListView: React.FC<TimelineListViewProps> = ({
+ headerText,
+ timelines,
+}) => {
+ const { t } = useTranslation();
+
+ return (
+ <div className="home-v2-timeline-list">
+ <div className="home-v2-timeline-list-item">
+ <svg className="home-v2-timeline-list-item-line" viewBox="0 0 120 120">
+ <path
+ d="M 0,20 Q 80,20 80,80 l 0,40"
+ stroke="#007bff"
+ strokeWidth="40"
+ fill="none"
+ />
+ </svg>
+ <h3>{convertI18nText(headerText, t)}</h3>
+ </div>
+ {timelines != null
+ ? timelines.map((t) => <TimelineListItem key={t.name} timeline={t} />)
+ : null}
+ <TimelineListArrow />
+ </div>
+ );
+};
+
+export default TimelineListView;
diff --git a/FrontEnd/src/app/views/home/WebsiteIntroduction.tsx b/FrontEnd/src/app/views/home/WebsiteIntroduction.tsx
new file mode 100644
index 00000000..f4ceebcc
--- /dev/null
+++ b/FrontEnd/src/app/views/home/WebsiteIntroduction.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+const WebsiteIntroduction: React.FC<{
+ className?: string;
+ style?: React.CSSProperties;
+}> = ({ className, style }) => {
+ const { i18n } = useTranslation();
+
+ if (i18n.language.startsWith("zh")) {
+ return (
+ <div className={className} style={style}>
+ <h2>欢迎来到时间线!🎉🎉🎉</h2>
+ <p>
+ 本网站由无数个独立的时间线构成,每一个时间线都是一个消息列表,类似于一个聊天软件(比如QQ)。
+ </p>
+ <p>
+ 如果你拥有一个账号,登陆后你可以自由地在属于你的时间线中发送内容,支持markdown和上传图片哦!你可以创建一个新的时间线来开启一个新的话题。你也可以设置相关权限,只让一部分人能看到时间线的内容。
+ </p>
+ <p>
+ 如果你没有账号,那么你可以去浏览一下公开的时间线,比如下面这些站长设置的高光时间线。
+ </p>
+ <p>
+ 鉴于这个网站在我的小型服务器上部署,所以没有开放注册。如果你也想把这个服务部署到自己的服务器上,你可以在关于页面找到一些信息。
+ </p>
+ <p>
+ <small className="text-secondary">
+ 这一段介绍是我的对象抱怨多次我的网站他根本看不明白之后加的,希望你能顺利看懂这个网站的逻辑!😅
+ </small>
+ </p>
+ </div>
+ );
+ } else {
+ return (
+ <div className={className} style={style}>
+ <h2>Welcome to Timeline!🎉🎉🎉</h2>
+ <p>
+ This website consists of many individual timelines. Each timeline is a
+ list of messages just like a chat app.
+ </p>
+ <p>
+ If you do have an account, you can post messages, which supports
+ Markdown and images, in your timelines after logging in. You can also
+ create a new timeline to open a new topic. You can set the permission
+ of a timeline to only allow specified people to see the content of the
+ timeline.
+ </p>
+ <p>
+ If you don&apos;t have an account, you can view some public timelines
+ like highlight timelines below set by website manager.
+ </p>
+ <p>
+ Since this website is hosted on my tiny server, so account registry is
+ not opened. If you want to host this service on your own server, you
+ can find some useful information on about page.
+ </p>
+ <p>
+ <small className="text-secondary">
+ This introduction is added after my lover complained a lot of times
+ about the obscuration of my website. May you understand the logic of
+ it!😅
+ </small>
+ </p>
+ </div>
+ );
+ }
+};
+
+export default WebsiteIntroduction;
diff --git a/FrontEnd/src/app/views/home/home.sass b/FrontEnd/src/app/views/home/home.sass
index 4b86f241..56049994 100644
--- a/FrontEnd/src/app/views/home/home.sass
+++ b/FrontEnd/src/app/views/home/home.sass
@@ -1,36 +1,29 @@
-.timeline-board
- @extend .cru-card
- @extend .d-flex
- @extend .flex-column
- @extend .py-3
- min-height: 200px
- height: 100%
- position: relative
-
-.timeline-board-header
- @extend .px-3
+.home-v2-timeline-list-item
display: flex
align-items: center
- justify-content: space-between
-.timeline-board-item
- font-size: 1.1em
- @extend .px-3
- height: 48px
- transition: background 0.3s
- display: flex
- align-items: center
- .icon
- height: 1.3em
- color: black
- @extend .mr-2
+.home-v2-timeline-list-item-timeline
+ transition: background 0.8s
+ animation: 0.8s home-v2-timeline-list-item-timeline-enter
&:hover
- background: $gray-300
- .right
- display: flex
- align-items: center
- flex-shrink: 0
- .title
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
+ background: $gray-200
+
+@keyframes home-v2-timeline-list-item-timeline-enter
+ from
+ transform: translate(-100%,0)
+ opacity: 0
+
+.home-v2-timeline-list-item-line
+ width: 80px
+ flex-shrink: 0
+
+@keyframes home-v2-timeline-list-loading-head-animation
+ from
+ transform: translate(0,-30px)
+ opacity: 1
+
+ to
+ opacity: 0
+
+.home-v2-timeline-list-loading-head
+ animation: 1s infinite home-v2-timeline-list-loading-head-animation
diff --git a/FrontEnd/src/app/views/home/index.tsx b/FrontEnd/src/app/views/home/index.tsx
index bcf6ad6e..a0df6a5a 100644
--- a/FrontEnd/src/app/views/home/index.tsx
+++ b/FrontEnd/src/app/views/home/index.tsx
@@ -1,16 +1,25 @@
import React from "react";
import { useHistory } from "react-router";
import { useTranslation } from "react-i18next";
-import { Row, Container, Button, Col } from "react-bootstrap";
+import { Container, Button, Row, Col } from "react-bootstrap";
+
+import { HttpTimelineInfo } from "@/http/timeline";
+import { getHttpHighlightClient } from "@/http/highlight";
import { useUser } from "@/services/user";
+
import SearchInput from "../common/SearchInput";
+import TimelineCreateDialog from "../center/TimelineCreateDialog";
+import TimelineListView from "./TimelineListView";
+import WebsiteIntroduction from "./WebsiteIntroduction";
-import BoardWithoutUser from "./BoardWithoutUser";
-import BoardWithUser from "./BoardWithUser";
-import TimelineCreateDialog from "./TimelineCreateDialog";
+const highlightTimelineMessageMap = {
+ loading: "home.loadingHighlightTimelines",
+ done: "home.loadedHighlightTimelines",
+ error: "home.errorHighlightTimelines",
+} as const;
-const HomePage: React.FC = () => {
+const HomeV2: React.FC = () => {
const history = useHistory();
const { t } = useTranslation();
@@ -21,13 +30,44 @@ const HomePage: React.FC = () => {
const [dialog, setDialog] = React.useState<"create" | null>(null);
+ const [highlightTimelineState, setHighlightTimelineState] = React.useState<
+ "loading" | "done" | "error"
+ >("loading");
+ const [highlightTimelines, setHighlightTimelines] = React.useState<
+ HttpTimelineInfo[] | undefined
+ >();
+
+ React.useEffect(() => {
+ if (highlightTimelineState === "loading") {
+ let subscribe = true;
+ void getHttpHighlightClient()
+ .list()
+ .then(
+ (data) => {
+ if (subscribe) {
+ setHighlightTimelineState("done");
+ setHighlightTimelines(data);
+ }
+ },
+ () => {
+ if (subscribe) {
+ setHighlightTimelineState("error");
+ setHighlightTimelines(undefined);
+ }
+ }
+ );
+ return () => {
+ subscribe = false;
+ };
+ }
+ }, [highlightTimelineState]);
+
return (
<>
- <Container>
- <Row className="my-3 justify-content-center">
- <Col xs={12} sm={8} lg={6}>
+ <Container fluid className="px-0">
+ <Row className="mx-0 my-3 px-2 justify-content-end">
+ <Col xs="12" sm="auto">
<SearchInput
- className="justify-content-center"
value={navText}
onChange={setNavText}
onButtonClick={() => {
@@ -48,24 +88,17 @@ const HomePage: React.FC = () => {
/>
</Col>
</Row>
- {(() => {
- if (user == null) {
- return <BoardWithoutUser />;
- } else {
- return <BoardWithUser user={user} />;
- }
- })()}
+ <WebsiteIntroduction className="p-2" />
+ <TimelineListView
+ headerText={highlightTimelineMessageMap[highlightTimelineState]}
+ timelines={highlightTimelines}
+ />
</Container>
{dialog === "create" && (
- <TimelineCreateDialog
- open
- close={() => {
- setDialog(null);
- }}
- />
+ <TimelineCreateDialog open close={() => setDialog(null)} />
)}
</>
);
};
-export default HomePage;
+export default HomeV2;