diff options
author | crupest <crupest@outlook.com> | 2021-06-15 18:25:17 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-15 18:25:17 +0800 |
commit | 4645761c2090aeaf8c26789155b342c048f44535 (patch) | |
tree | 1aab5ce94549f3f8b3fd1a31c84fb2dd8b6b2511 /FrontEnd/src/views/search/index.tsx | |
parent | 485ef185153890b7c6ac4ed9798a3f2db80c8845 (diff) | |
parent | b6afd5e8161126522bdfff876f5483fa97e94797 (diff) | |
download | timeline-4645761c2090aeaf8c26789155b342c048f44535.tar.gz timeline-4645761c2090aeaf8c26789155b342c048f44535.tar.bz2 timeline-4645761c2090aeaf8c26789155b342c048f44535.zip |
Merge pull request #620 from crupest/vite
Migrate to vite!
Diffstat (limited to 'FrontEnd/src/views/search/index.tsx')
-rw-r--r-- | FrontEnd/src/views/search/index.tsx | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/FrontEnd/src/views/search/index.tsx b/FrontEnd/src/views/search/index.tsx new file mode 100644 index 00000000..f5018c3e --- /dev/null +++ b/FrontEnd/src/views/search/index.tsx @@ -0,0 +1,130 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Container, Row } from "react-bootstrap"; +import { useHistory, useLocation } from "react-router"; +import { Link } from "react-router-dom"; + +import { HttpNetworkError } from "@/http/common"; +import { getHttpSearchClient } from "@/http/search"; +import { HttpTimelineInfo } from "@/http/timeline"; + +import SearchInput from "../common/SearchInput"; +import UserAvatar from "../common/user/UserAvatar"; + +import "./index.css"; + +const TimelineSearchResultItemView: React.FC<{ + timeline: HttpTimelineInfo; +}> = ({ timeline }) => { + const link = timeline.name.startsWith("src") + ? `users/${timeline.owner.username}` + : `timelines/${timeline.name}`; + + return ( + <div className="timeline-search-result-item my-2 p-3"> + <h4> + <Link to={link} className="mb-2 text-primary"> + {timeline.title} + <small className="ms-3 text-secondary">{timeline.name}</small> + </Link> + </h4> + <div> + <UserAvatar + username={timeline.owner.username} + className="timeline-search-result-item-avatar me-2" + /> + {timeline.owner.nickname} + <small className="ms-3 text-secondary"> + src{timeline.owner.username} + </small> + </div> + </div> + ); +}; + +const SearchPage: React.FC = () => { + const { t } = useTranslation(); + + const history = useHistory(); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const queryParam = searchParams.get("q"); + + const [searchText, setSearchText] = React.useState<string>(""); + const [state, setState] = React.useState< + HttpTimelineInfo[] | "init" | "loading" | "network-error" | "error" + >("init"); + + const [forceResearchKey, setForceResearchKey] = React.useState<number>(0); + + React.useEffect(() => { + setState("init"); + if (queryParam != null && queryParam.length > 0) { + setSearchText(queryParam); + setState("loading"); + void getHttpSearchClient() + .searchTimelines(queryParam) + .then( + (ts) => { + setState(ts); + }, + (e) => { + if (e instanceof HttpNetworkError) { + setState("network-error"); + } else { + setState("error"); + } + } + ); + } + }, [queryParam, forceResearchKey]); + + return ( + <Container className="my-3"> + <Row className="justify-content-center"> + <SearchInput + className="col-12 col-sm-9 col-md-6" + value={searchText} + onChange={setSearchText} + loading={state === "loading"} + onButtonClick={() => { + if (queryParam === searchText) { + setForceResearchKey((old) => old + 1); + } else { + history.push(`/search?q=${searchText}`); + } + }} + /> + </Row> + {(() => { + switch (state) { + case "init": { + if (queryParam == null || queryParam.length === 0) { + return <div>{t("searchPage.input")}</div>; + } + break; + } + case "loading": { + return <div>{t("searchPage.loading")}</div>; + } + case "network-error": { + return <div className="text-danger">{t("error.network")}</div>; + } + case "error": { + return <div className="text-danger">{t("error.unknown")}</div>; + } + default: { + if (state.length === 0) { + return <div>{t("searchPage.noResult")}</div>; + } + return state.map((t) => ( + <TimelineSearchResultItemView key={t.name} timeline={t} /> + )); + } + } + })()} + </Container> + ); +}; + +export default SearchPage; |