diff options
Diffstat (limited to 'FrontEnd/src/app/views/home')
-rw-r--r-- | FrontEnd/src/app/views/home/BoardWithUser.tsx | 111 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/BoardWithoutUser.tsx | 33 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/TimelineBoard.tsx | 370 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/TimelineCreateDialog.tsx | 53 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/TimelineListView.tsx | 101 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/WebsiteIntroduction.tsx | 69 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/home.sass | 57 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/index.tsx | 79 |
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'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; |