aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views/search/index.tsx
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-06-15 18:25:17 +0800
committerGitHub <noreply@github.com>2021-06-15 18:25:17 +0800
commit4645761c2090aeaf8c26789155b342c048f44535 (patch)
tree1aab5ce94549f3f8b3fd1a31c84fb2dd8b6b2511 /FrontEnd/src/views/search/index.tsx
parent485ef185153890b7c6ac4ed9798a3f2db80c8845 (diff)
parentb6afd5e8161126522bdfff876f5483fa97e94797 (diff)
downloadtimeline-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.tsx130
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;