diff --git a/web/package.json b/web/package.json index ff7670bf..7590dc7e 100644 --- a/web/package.json +++ b/web/package.json @@ -41,6 +41,7 @@ "react-leaflet": "^4.2.1", "react-redux": "^9.1.2", "react-router-dom": "^6.27.0", + "react-simple-pull-to-refresh": "^1.3.3", "react-use": "^17.5.1", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.14", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 65136f38..9b299b5d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: react-router-dom: specifier: ^6.27.0 version: 6.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-simple-pull-to-refresh: + specifier: ^1.3.3 + version: 1.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-use: specifier: ^17.5.1 version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2968,6 +2971,12 @@ packages: peerDependencies: react: '>=16.8' + react-simple-pull-to-refresh@1.3.3: + resolution: {integrity: sha512-6qXsa5RtNVmKJhLWvDLIX8UK51HFtCEGjdqQGf+M1Qjrcc4qH4fki97sgVpGEFBRwbY7DiVDA5N5p97kF16DTw==} + peerDependencies: + react: ^16.10.2 || ^17.0.0 || ^18.0.0 + react-dom: ^16.10.2 || ^17.0.0 || ^18.0.0 + react-style-singleton@2.2.1: resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -6449,6 +6458,11 @@ snapshots: '@remix-run/router': 1.20.0 react: 18.3.1 + react-simple-pull-to-refresh@1.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-style-singleton@2.2.1(@types/react@18.3.12)(react@18.3.1): dependencies: get-nonce: 1.0.1 diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index 5f6617aa..024d2f77 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -1,6 +1,7 @@ import { Button } from "@usememos/mui"; import { ArrowDownIcon, LoaderIcon } from "lucide-react"; import { useEffect, useState } from "react"; +import PullToRefresh from "react-simple-pull-to-refresh"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { useMemoList, useMemoStore } from "@/store/v1"; import { Memo } from "@/types/proto/api/v1/memo_service"; @@ -28,7 +29,11 @@ const PagedMemoList = (props: Props) => { nextPageToken: "", }); const sortedMemoList = props.listSort ? props.listSort(memoList.value) : memoList.value; - + const handleRefresh = async () => { + memoList.reset(); + setState((state) => ({ ...state, nextPageToken: "" })); + fetchMoreMemos(""); + }; const setIsRequesting = (isRequesting: boolean) => { setState((state) => ({ ...state, isRequesting })); }; @@ -53,28 +58,38 @@ const PagedMemoList = (props: Props) => { }, [props.filter, props.pageSize]); return ( - <> - {sortedMemoList.map((memo) => props.renderer(memo))} - {state.isRequesting && ( + } + refreshingContent={
- +
- )} - {!state.isRequesting && state.nextPageToken && ( -
- -
- )} - {!state.isRequesting && !state.nextPageToken && sortedMemoList.length === 0 && ( -
- -

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

-
- )} - + } + > + <> + {sortedMemoList.map((memo) => props.renderer(memo))} + {state.isRequesting && ( +
+ +
+ )} + {!state.isRequesting && state.nextPageToken && ( +
+ +
+ )} + {!state.isRequesting && !state.nextPageToken && sortedMemoList.length === 0 && ( +
+ +

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

+
+ )} + +
); };