mirror of
https://github.com/usememos/memos.git
synced 2025-03-19 12:10:08 +01:00
refactor: paged memo list container
This commit is contained in:
parent
41976cb894
commit
339c38750f
81
web/src/components/PagedMemoList/PagedMemoList.tsx
Normal file
81
web/src/components/PagedMemoList/PagedMemoList.tsx
Normal file
@ -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<State>({
|
||||
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 && (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
loading={state.isRequesting}
|
||||
endDecorator={<ArrowDownIcon className="w-4 h-auto" />}
|
||||
onClick={() => fetchMoreMemos()}
|
||||
>
|
||||
{t("memo.load-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!state.nextPageToken && sortedMemoList.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PagedMemoList;
|
3
web/src/components/PagedMemoList/index.ts
Normal file
3
web/src/components/PagedMemoList/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import PagedMemoList from "./PagedMemoList";
|
||||
|
||||
export default PagedMemoList;
|
@ -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<string>("");
|
||||
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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
<MemoFilters />
|
||||
{sortedMemos.map((memo) => (
|
||||
<div
|
||||
key={memo.name}
|
||||
className="relative flex flex-col justify-start items-start w-full p-4 pt-3 mb-2 bg-white dark:bg-zinc-800 rounded-lg"
|
||||
>
|
||||
<div className="w-full mb-1 flex flex-row justify-between items-center">
|
||||
<div className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1">
|
||||
<div className="text-sm leading-6 text-gray-400 select-none">
|
||||
<relative-time datetime={memo.displayTime?.toISOString()} tense="past"></relative-time>
|
||||
<PagedMemoList
|
||||
renderer={(memo: Memo) => (
|
||||
<div
|
||||
key={memo.name}
|
||||
className="relative flex flex-col justify-start items-start w-full p-4 pt-3 mb-2 bg-white dark:bg-zinc-800 rounded-lg"
|
||||
>
|
||||
<div className="w-full mb-1 flex flex-row justify-between items-center">
|
||||
<div className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1">
|
||||
<div className="text-sm leading-6 text-gray-400 select-none">
|
||||
<relative-time datetime={memo.displayTime?.toISOString()} tense="past"></relative-time>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end items-center gap-x-2">
|
||||
<Tooltip title={t("common.restore")} placement="top">
|
||||
<button onClick={() => handleRestoreMemoClick(memo)}>
|
||||
<ArchiveRestoreIcon className="w-4 h-auto cursor-pointer text-gray-500 dark:text-gray-400" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.delete")} placement="top">
|
||||
<button onClick={() => handleDeleteMemoClick(memo)} className="text-gray-500 dark:text-gray-400">
|
||||
<TrashIcon className="w-4 h-auto cursor-pointer" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end items-center gap-x-2">
|
||||
<Tooltip title={t("common.restore")} placement="top">
|
||||
<button onClick={() => handleRestoreMemoClick(memo)}>
|
||||
<ArchiveRestoreIcon className="w-4 h-auto cursor-pointer text-gray-500 dark:text-gray-400" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.delete")} placement="top">
|
||||
<button onClick={() => handleDeleteMemoClick(memo)} className="text-gray-500 dark:text-gray-400">
|
||||
<TrashIcon className="w-4 h-auto cursor-pointer" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<MemoContent key={`${memo.name}-${memo.displayTime}`} memoName={memo.name} nodes={memo.nodes} readonly={true} />
|
||||
</div>
|
||||
<MemoContent key={`${memo.name}-${memo.displayTime}`} memoName={memo.name} nodes={memo.nodes} readonly={true} />
|
||||
</div>
|
||||
))}
|
||||
{nextPageToken && (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
loading={isRequesting}
|
||||
endDecorator={<ArrowDownIcon className="w-4 h-auto" />}
|
||||
onClick={() => fetchMemos(nextPageToken)}
|
||||
>
|
||||
{t("memo.load-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!nextPageToken && sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -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<string>("");
|
||||
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 (
|
||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
||||
@ -75,28 +51,19 @@ const Explore = () => {
|
||||
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||
<MemoFilters />
|
||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||
{sortedMemos.map((memo) => (
|
||||
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility showPinned compact />
|
||||
))}
|
||||
{nextPageToken && (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
loading={isRequesting}
|
||||
endDecorator={<ArrowDownIcon className="w-4 h-auto" />}
|
||||
onClick={() => fetchMemos(nextPageToken)}
|
||||
>
|
||||
{t("memo.load-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!nextPageToken && sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)}
|
||||
<PagedMemoList
|
||||
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showCreator showVisibility compact />}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{md && (
|
||||
|
@ -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<string>("");
|
||||
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 (
|
||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
||||
@ -91,28 +63,20 @@ const Home = () => {
|
||||
<MemoEditor className="mb-2" cacheKey="home-memo-editor" />
|
||||
<MemoFilters />
|
||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||
{sortedMemos.map((memo) => (
|
||||
<MemoView key={`${memo.name}-${memo.updateTime}`} memo={memo} showVisibility showPinned compact />
|
||||
))}
|
||||
{nextPageToken && (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
loading={isRequesting}
|
||||
endDecorator={<ArrowDownIcon className="w-4 h-auto" />}
|
||||
onClick={() => fetchMemos(nextPageToken)}
|
||||
>
|
||||
{t("memo.load-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!nextPageToken && sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)}
|
||||
<PagedMemoList
|
||||
renderer={(memo: Memo) => <MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />}
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{md && (
|
||||
|
@ -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<User>();
|
||||
const memoStore = useMemoStore();
|
||||
const memoList = useMemoList();
|
||||
const memoFilterStore = useMemoFilterStore();
|
||||
const [isRequesting, setIsRequesting] = useState(true);
|
||||
const [nextPageToken, setNextPageToken] = useState<string>("");
|
||||
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 = () => {
|
||||
</div>
|
||||
</div>
|
||||
<MemoFilters />
|
||||
{sortedMemos.map((memo) => (
|
||||
<MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />
|
||||
))}
|
||||
{nextPageToken && (
|
||||
<div className="w-full flex flex-row justify-center items-center my-4">
|
||||
<Button
|
||||
variant="plain"
|
||||
color="neutral"
|
||||
loading={isRequesting}
|
||||
endDecorator={<ArrowDownIcon className="w-4 h-auto" />}
|
||||
onClick={() => fetchMemos(nextPageToken)}
|
||||
>
|
||||
{t("memo.load-more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!nextPageToken && sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)}
|
||||
<PagedMemoList
|
||||
renderer={(memo: Memo) => (
|
||||
<MemoView key={`${memo.name}-${memo.displayTime}`} memo={memo} showVisibility showPinned compact />
|
||||
)}
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p>Not found</p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user