From 339c38750f8b703c6ca81570d1738cfe797284cc Mon Sep 17 00:00:00 2001 From: johnnyjoy Date: Sun, 22 Sep 2024 19:30:39 +0800 Subject: [PATCH] refactor: paged memo list container --- .../PagedMemoList/PagedMemoList.tsx | 81 ++++++++++++ web/src/components/PagedMemoList/index.ts | 3 + web/src/pages/Archived.tsx | 118 +++++++----------- web/src/pages/Explore.tsx | 75 ++++------- web/src/pages/Home.tsx | 78 ++++-------- web/src/pages/UserProfile.tsx | 80 ++++-------- 6 files changed, 196 insertions(+), 239 deletions(-) create mode 100644 web/src/components/PagedMemoList/PagedMemoList.tsx create mode 100644 web/src/components/PagedMemoList/index.ts diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx new file mode 100644 index 00000000..20033c64 --- /dev/null +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -0,0 +1,81 @@ +import { Button } from "@mui/joy"; +import { ArrowDownIcon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; +import { useMemoList, useMemoStore } from "@/store/v1"; +import { Memo } from "@/types/proto/api/v1/memo_service"; +import { useTranslate } from "@/utils/i18n"; +import Empty from "../Empty"; + +interface Props { + renderer: (memo: Memo) => JSX.Element; + listSort?: (list: Memo[]) => Memo[]; + filter?: string; + pageSize?: number; +} + +interface State { + isRequesting: boolean; + nextPageToken: string; +} + +const PagedMemoList = (props: Props) => { + const t = useTranslate(); + const memoStore = useMemoStore(); + const memoList = useMemoList(); + const [state, setState] = useState({ + isRequesting: false, + nextPageToken: "", + }); + const sortedMemoList = props.listSort ? props.listSort(memoList.value) : memoList.value; + + const setIsRequesting = (isRequesting: boolean) => { + setState((state) => ({ ...state, isRequesting })); + }; + + const fetchMoreMemos = async () => { + setIsRequesting(true); + const response = await memoStore.fetchMemos({ + filter: props.filter || "", + pageSize: props.pageSize || DEFAULT_LIST_MEMOS_PAGE_SIZE, + pageToken: state.nextPageToken, + }); + setState(() => ({ + isRequesting: false, + nextPageToken: response.nextPageToken, + })); + }; + + useEffect(() => { + memoList.reset(); + setState((state) => ({ ...state, nextPageToken: "" })); + fetchMoreMemos(); + }, [props.filter, props.pageSize]); + + return ( + <> + {sortedMemoList.map((memo) => props.renderer(memo))} + {state.nextPageToken && ( +
+ +
+ )} + {!state.nextPageToken && sortedMemoList.length === 0 && ( +
+ +

{t("message.no-data")}

+
+ )} + + ); +}; + +export default PagedMemoList; diff --git a/web/src/components/PagedMemoList/index.ts b/web/src/components/PagedMemoList/index.ts new file mode 100644 index 00000000..fa75beec --- /dev/null +++ b/web/src/components/PagedMemoList/index.ts @@ -0,0 +1,3 @@ +import PagedMemoList from "./PagedMemoList"; + +export default PagedMemoList; diff --git a/web/src/pages/Archived.tsx b/web/src/pages/Archived.tsx index e0032309..db601bd1 100644 --- a/web/src/pages/Archived.tsx +++ b/web/src/pages/Archived.tsx @@ -1,17 +1,16 @@ -import { Button, Tooltip } from "@mui/joy"; +import { Tooltip } from "@mui/joy"; import dayjs from "dayjs"; -import { ArchiveIcon, ArchiveRestoreIcon, ArrowDownIcon, TrashIcon } from "lucide-react"; +import { ArchiveIcon, ArchiveRestoreIcon, TrashIcon } from "lucide-react"; import { ClientError } from "nice-grpc-web"; -import { useEffect, useState } from "react"; +import { useMemo } from "react"; import toast from "react-hot-toast"; -import Empty from "@/components/Empty"; import MemoContent from "@/components/MemoContent"; import MemoFilters from "@/components/MemoFilters"; import MobileHeader from "@/components/MobileHeader"; +import PagedMemoList from "@/components/PagedMemoList"; import SearchBar from "@/components/SearchBar"; -import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import useCurrentUser from "@/hooks/useCurrentUser"; -import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1"; +import { useMemoFilterStore, useMemoStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v1/common"; import { Memo } from "@/types/proto/api/v1/memo_service"; import { useTranslate } from "@/utils/i18n"; @@ -20,25 +19,9 @@ const Archived = () => { const t = useTranslate(); const user = useCurrentUser(); const memoStore = useMemoStore(); - const memoList = useMemoList(); const memoFilterStore = useMemoFilterStore(); - const [isRequesting, setIsRequesting] = useState(true); - const [nextPageToken, setNextPageToken] = useState(""); - const sortedMemos = memoList.value - .filter((memo) => memo.rowStatus === RowStatus.ARCHIVED) - .sort((a, b) => - memoFilterStore.orderByTimeAsc - ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() - : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), - ); - useEffect(() => { - memoList.reset(); - fetchMemos(""); - }, [memoFilterStore.filters]); - - const fetchMemos = async (nextPageToken: string) => { - setIsRequesting(true); + const memoListFilter = useMemo(() => { const filters = [`creator == "${user.name}"`, `row_status == "ARCHIVED"`]; const contentSearch: string[] = []; const tagSearch: string[] = []; @@ -58,14 +41,8 @@ const Archived = () => { if (tagSearch.length > 0) { filters.push(`tag_search == [${tagSearch.join(", ")}]`); } - const response = await memoStore.fetchMemos({ - pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, - filter: filters.join(" && "), - pageToken: nextPageToken, - }); - setIsRequesting(false); - setNextPageToken(response.nextPageToken); - }; + return filters.join(" && "); + }, [user, memoFilterStore.filters]); const handleDeleteMemoClick = async (memo: Memo) => { const confirmed = window.confirm(t("memo.delete-confirm")); @@ -105,52 +82,45 @@ const Archived = () => { - {sortedMemos.map((memo) => ( -
-
-
-
- + ( +
+
+
+
+ +
+
+
+ + + + + +
-
- - - - - - -
+
- -
- ))} - {nextPageToken && ( -
- -
- )} - {!nextPageToken && sortedMemos.length === 0 && ( -
- -

{t("message.no-data")}

-
- )} + )} + listSort={(memos: Memo[]) => + memos + .filter((memo) => memo.rowStatus === RowStatus.ARCHIVED) + .sort((a, b) => + memoFilterStore.orderByTimeAsc + ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() + : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), + ) + } + filter={memoListFilter} + />
diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index c7312a58..fc754ceb 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -1,41 +1,23 @@ -import { Button } from "@mui/joy"; import clsx from "clsx"; import dayjs from "dayjs"; -import { ArrowDownIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import Empty from "@/components/Empty"; +import { useMemo } from "react"; import { ExploreSidebar, ExploreSidebarDrawer } from "@/components/ExploreSidebar"; import MemoFilters from "@/components/MemoFilters"; import MemoView from "@/components/MemoView"; import MobileHeader from "@/components/MobileHeader"; -import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; +import PagedMemoList from "@/components/PagedMemoList"; import useCurrentUser from "@/hooks/useCurrentUser"; import useResponsiveWidth from "@/hooks/useResponsiveWidth"; -import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1"; -import { useTranslate } from "@/utils/i18n"; +import { useMemoFilterStore } from "@/store/v1"; +import { RowStatus } from "@/types/proto/api/v1/common"; +import { Memo } from "@/types/proto/api/v1/memo_service"; const Explore = () => { - const t = useTranslate(); const { md } = useResponsiveWidth(); const user = useCurrentUser(); - const memoStore = useMemoStore(); - const memoList = useMemoList(); const memoFilterStore = useMemoFilterStore(); - const [isRequesting, setIsRequesting] = useState(true); - const [nextPageToken, setNextPageToken] = useState(""); - const sortedMemos = memoList.value.sort((a, b) => - memoFilterStore.orderByTimeAsc - ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() - : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), - ); - useEffect(() => { - memoList.reset(); - fetchMemos(""); - }, [memoFilterStore.filters]); - - const fetchMemos = async (nextPageToken: string) => { - setIsRequesting(true); + const memoListFilter = useMemo(() => { const filters = [`row_status == "NORMAL"`, `visibilities == [${user ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; const contentSearch: string[] = []; const tagSearch: string[] = []; @@ -55,14 +37,8 @@ const Explore = () => { if (tagSearch.length > 0) { filters.push(`tag_search == [${tagSearch.join(", ")}]`); } - const response = await memoStore.fetchMemos({ - pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, - filter: filters.join(" && "), - pageToken: nextPageToken, - }); - setIsRequesting(false); - setNextPageToken(response.nextPageToken); - }; + return filters.join(" && "); + }, [user, memoFilterStore.filters, memoFilterStore.orderByTimeAsc]); return (
@@ -75,28 +51,19 @@ const Explore = () => {
- {sortedMemos.map((memo) => ( - - ))} - {nextPageToken && ( -
- -
- )} - {!nextPageToken && sortedMemos.length === 0 && ( -
- -

{t("message.no-data")}

-
- )} + } + listSort={(memos: Memo[]) => + memos + .filter((memo) => memo.rowStatus === RowStatus.ACTIVE) + .sort((a, b) => + memoFilterStore.orderByTimeAsc + ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() + : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), + ) + } + filter={memoListFilter} + />
{md && ( diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx index 4cf762e4..24fc7d52 100644 --- a/web/src/pages/Home.tsx +++ b/web/src/pages/Home.tsx @@ -1,46 +1,24 @@ -import { Button } from "@mui/joy"; import clsx from "clsx"; import dayjs from "dayjs"; -import { ArrowDownIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import Empty from "@/components/Empty"; +import { useMemo } from "react"; import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar"; import MemoEditor from "@/components/MemoEditor"; import MemoFilters from "@/components/MemoFilters"; import MemoView from "@/components/MemoView"; import MobileHeader from "@/components/MobileHeader"; -import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; +import PagedMemoList from "@/components/PagedMemoList"; import useCurrentUser from "@/hooks/useCurrentUser"; import useResponsiveWidth from "@/hooks/useResponsiveWidth"; -import { useMemoFilterStore, useMemoList, useMemoStore } from "@/store/v1"; +import { useMemoFilterStore } from "@/store/v1"; import { RowStatus } from "@/types/proto/api/v1/common"; -import { useTranslate } from "@/utils/i18n"; +import { Memo } from "@/types/proto/api/v1/memo_service"; const Home = () => { - const t = useTranslate(); const { md } = useResponsiveWidth(); const user = useCurrentUser(); - const memoStore = useMemoStore(); - const memoList = useMemoList(); const memoFilterStore = useMemoFilterStore(); - const [isRequesting, setIsRequesting] = useState(true); - const [nextPageToken, setNextPageToken] = useState(""); - const sortedMemos = memoList.value - .filter((memo) => memo.rowStatus === RowStatus.ACTIVE) - .sort((a, b) => - memoFilterStore.orderByTimeAsc - ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() - : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), - ) - .sort((a, b) => Number(b.pinned) - Number(a.pinned)); - useEffect(() => { - memoList.reset(); - fetchMemos(""); - }, [memoFilterStore.filters]); - - const fetchMemos = async (nextPageToken: string) => { - setIsRequesting(true); + const memoListFilter = useMemo(() => { const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; const tagSearch: string[] = []; @@ -70,14 +48,8 @@ const Home = () => { if (tagSearch.length > 0) { filters.push(`tag_search == [${tagSearch.join(", ")}]`); } - const response = await memoStore.fetchMemos({ - pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, - filter: filters.join(" && "), - pageToken: nextPageToken, - }); - setIsRequesting(false); - setNextPageToken(response.nextPageToken); - }; + return filters.join(" && "); + }, [user, memoFilterStore.filters, memoFilterStore.orderByTimeAsc]); return (
@@ -91,28 +63,20 @@ const Home = () => {
- {sortedMemos.map((memo) => ( - - ))} - {nextPageToken && ( -
- -
- )} - {!nextPageToken && sortedMemos.length === 0 && ( -
- -

{t("message.no-data")}

-
- )} + } + listSort={(memos: Memo[]) => + memos + .filter((memo) => memo.rowStatus === RowStatus.ACTIVE) + .sort((a, b) => + memoFilterStore.orderByTimeAsc + ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() + : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), + ) + .sort((a, b) => Number(b.pinned) - Number(a.pinned)) + } + filter={memoListFilter} + />
{md && ( diff --git a/web/src/pages/UserProfile.tsx b/web/src/pages/UserProfile.tsx index 929529b8..46707416 100644 --- a/web/src/pages/UserProfile.tsx +++ b/web/src/pages/UserProfile.tsx @@ -1,18 +1,19 @@ import { Button } from "@mui/joy"; import copy from "copy-to-clipboard"; import dayjs from "dayjs"; -import { ArrowDownIcon, ExternalLinkIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { ExternalLinkIcon } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { useParams } from "react-router-dom"; -import Empty from "@/components/Empty"; import MemoFilters from "@/components/MemoFilters"; import MemoView from "@/components/MemoView"; import MobileHeader from "@/components/MobileHeader"; +import PagedMemoList from "@/components/PagedMemoList"; import UserAvatar from "@/components/UserAvatar"; -import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import useLoading from "@/hooks/useLoading"; -import { useMemoFilterStore, useMemoList, useMemoStore, useUserStore } from "@/store/v1"; +import { useMemoFilterStore, useUserStore } from "@/store/v1"; +import { RowStatus } from "@/types/proto/api/v1/common"; +import { Memo } from "@/types/proto/api/v1/memo_service"; import { User } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; @@ -22,14 +23,7 @@ const UserProfile = () => { const userStore = useUserStore(); const loadingState = useLoading(); const [user, setUser] = useState(); - const memoStore = useMemoStore(); - const memoList = useMemoList(); const memoFilterStore = useMemoFilterStore(); - const [isRequesting, setIsRequesting] = useState(true); - const [nextPageToken, setNextPageToken] = useState(""); - const sortedMemos = memoList.value - .sort((a, b) => dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix()) - .sort((a, b) => Number(b.pinned) - Number(a.pinned)); useEffect(() => { const username = params.username; @@ -53,21 +47,11 @@ const UserProfile = () => { }); }, [params.username]); - useEffect(() => { + const memoListFilter = useMemo(() => { if (!user) { - return; + return ""; } - memoList.reset(); - fetchMemos(""); - }, [user, memoFilterStore.filters]); - - const fetchMemos = async (nextPageToken: string) => { - if (!user) { - return; - } - - setIsRequesting(true); const filters = [`creator == "${user.name}"`, `row_status == "NORMAL"`, `order_by_pinned == true`]; const contentSearch: string[] = []; const tagSearch: string[] = []; @@ -84,14 +68,8 @@ const UserProfile = () => { if (tagSearch.length > 0) { filters.push(`tag_search == [${tagSearch.join(", ")}]`); } - const response = await memoStore.fetchMemos({ - pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE, - filter: filters.join(" && "), - pageToken: nextPageToken, - }); - setIsRequesting(false); - setNextPageToken(response.nextPageToken); - }; + return filters.join(" && "); + }, [user, memoFilterStore.filters]); const handleCopyProfileLink = () => { if (!user) { @@ -131,28 +109,22 @@ const UserProfile = () => { - {sortedMemos.map((memo) => ( - - ))} - {nextPageToken && ( -
- -
- )} - {!nextPageToken && sortedMemos.length === 0 && ( -
- -

{t("message.no-data")}

-
- )} + ( + + )} + listSort={(memos: Memo[]) => + memos + .filter((memo) => memo.rowStatus === RowStatus.ACTIVE) + .sort((a, b) => + memoFilterStore.orderByTimeAsc + ? dayjs(a.displayTime).unix() - dayjs(b.displayTime).unix() + : dayjs(b.displayTime).unix() - dayjs(a.displayTime).unix(), + ) + .sort((a, b) => Number(b.pinned) - Number(a.pinned)) + } + filter={memoListFilter} + /> ) : (

Not found