feat: pagination in nav

This commit is contained in:
Wentao Lyu 2023-03-15 04:05:14 +08:00
parent 71fc86b9a6
commit 8289558d94
8 changed files with 84 additions and 46 deletions

View file

@ -91,19 +91,17 @@ module.exports = {
} }
}, },
// getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(), // getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(),
getConvos: async (pageNumber = 1, pageSize = 12) => { getConvosByPage: async (pageNumber = 1, pageSize = 12) => {
try { try {
const skip = (pageNumber - 1) * pageSize; const totalConvos = await Conversation.countDocuments();
// const limit = pageNumber * pageSize; const totalPages = Math.ceil(totalConvos / pageSize);
const convos = await Conversation.find()
const conversations = await Conversation.find({})
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.skip(skip) .skip((pageNumber - 1) * pageSize)
// .limit(limit)
.limit(pageSize) .limit(pageSize)
.exec(); .exec();
return conversations; return { conversations: convos, pages: totalPages, pageNumber, pageSize };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { message: 'Error getting conversations' }; return { message: 'Error getting conversations' };

View file

@ -2,12 +2,12 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const { titleConvo } = require('../../app/'); const { titleConvo } = require('../../app/');
const { getConvo, saveConvo, getConvoTitle } = require('../../models'); const { getConvo, saveConvo, getConvoTitle } = require('../../models');
const { getConvos, deleteConvos, updateConvo } = require('../../models/Conversation'); const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
const { getMessages } = require('../../models/Message'); const { getMessages } = require('../../models/Message');
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
const pageNumber = req.query.pageNumber || 1; const pageNumber = req.query.pageNumber || 1;
res.status(200).send(await getConvos(pageNumber)); res.status(200).send(await getConvosByPage(pageNumber));
}); });
router.post('/gen_title', async (req, res) => { router.post('/gen_title', async (req, res) => {

View file

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import Conversation from './Conversation'; import Conversation from './Conversation';
export default function Conversations({ conversations, conversationId, showMore }) { export default function Conversations({ conversations, conversationId, pageNumber, pages, nextPage, previousPage, moveToTop }) {
const clickHandler = async (e) => { const clickHandler = (func) => async (e) => {
e.preventDefault(); e.preventDefault();
await showMore(); await func();
}; };
return ( return (
@ -33,18 +33,29 @@ export default function Conversations({ conversations, conversationId, showMore
chatGptLabel={convo.chatGptLabel} chatGptLabel={convo.chatGptLabel}
promptPrefix={convo.promptPrefix} promptPrefix={convo.promptPrefix}
bingData={bingData} bingData={bingData}
retainView={showMore.bind(null, false)} retainView={moveToTop}
/> />
); );
})} })}
{conversations?.length >= 12 && ( <div className="m-auto mt-4 mb-2 flex justify-center items-center gap-2">
<button <button
onClick={clickHandler} onClick={clickHandler(previousPage)}
className="btn btn-dark btn-small m-auto mb-2 flex justify-center gap-2" className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber<=1?" hidden-visibility":"")}
> disabled={pageNumber<=1}
Show more >
</button> &lt;&lt;
)} </button>
<span className="flex-none text-gray-400">
{pageNumber} / {pages}
</span>
<button
onClick={clickHandler(nextPage)}
className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber>=pages?" hidden-visibility":"")}
disabled={pageNumber>=pages}
>
&gt;&gt;
</button>
</div>
</> </>
); );
} }

View file

@ -7,7 +7,7 @@ import { setText } from '~/store/textSlice';
export default function MobileNav({ setNavVisible }) { export default function MobileNav({ setNavVisible }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { conversationId, convos } = useSelector((state) => state.convo); const { conversationId, convos, title } = useSelector((state) => state.convo);
const toggleNavVisible = () => { const toggleNavVisible = () => {
setNavVisible((prev) => { setNavVisible((prev) => {
@ -22,8 +22,6 @@ export default function MobileNav({ setNavVisible }) {
dispatch(setSubmission({})); dispatch(setSubmission({}));
} }
const title = convos?.find(element => element?.conversationId == conversationId)?.title || 'New Chat';
return ( return (
<div className="sticky top-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden"> <div className="sticky top-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden">
<button <button
@ -64,7 +62,7 @@ export default function MobileNav({ setNavVisible }) {
/> />
</svg> </svg>
</button> </button>
<h1 className="flex-1 text-center text-base font-normal">{title}</h1> <h1 className="flex-1 text-center text-base font-normal">{title || 'New Chat'}</h1>
<button <button
type="button" type="button"
className="px-3" className="px-3"

View file

@ -6,37 +6,53 @@ import NavLinks from './NavLinks';
import useDidMountEffect from '~/hooks/useDidMountEffect'; import useDidMountEffect from '~/hooks/useDidMountEffect';
import { swr } from '~/utils/fetchers'; import { swr } from '~/utils/fetchers';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { incrementPage, setConvos } from '~/store/convoSlice'; import { increasePage, decreasePage, setPage, setConvos, setPages } from '~/store/convoSlice';
export default function Nav({ navVisible, setNavVisible }) { export default function Nav({ navVisible, setNavVisible }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [isHovering, setIsHovering] = useState(false); const [isHovering, setIsHovering] = useState(false);
const { conversationId, convos, pageNumber, refreshConvoHint } = useSelector((state) => state.convo); const { conversationId, convos, pages, pageNumber, refreshConvoHint } = useSelector((state) => state.convo);
const onSuccess = (data) => { const onSuccess = (data) => {
dispatch(setConvos(data)); const { conversations, pages } = data;
if (pageNumber > pages)
dispatch(setPage(pages));
else
dispatch(setConvos(conversations));
dispatch(setPages(pages));
}; };
const { data, isLoading, mutate } = swr( const { data, isLoading, mutate } = swr(
`/api/convos?pageNumber=${pageNumber}`, `/api/convos?pageNumber=${pageNumber}`,
onSuccess onSuccess,
{revalidateOnMount: false}
); );
const containerRef = useRef(null); const containerRef = useRef(null);
const scrollPositionRef = useRef(null); const scrollPositionRef = useRef(null);
const showMore = async (increment = true) => { const moveToTop = () => {
const container = containerRef.current; const container = containerRef.current;
if (container) { if (container) {
scrollPositionRef.current = container.scrollTop; scrollPositionRef.current = container.scrollTop;
} }
}
if (increment) { const nextPage = async () => {
dispatch(incrementPage()); moveToTop()
await mutate();
} dispatch(increasePage());
await mutate();
}; };
useDidMountEffect(() => mutate(), [conversationId, refreshConvoHint]); const previousPage = async () => {
moveToTop()
dispatch(decreasePage());
await mutate();
};
useEffect(() => {mutate()}, [pageNumber, conversationId, refreshConvoHint]);
useEffect(() => { useEffect(() => {
const container = containerRef.current; const container = containerRef.current;
@ -86,8 +102,11 @@ export default function Nav({ navVisible, setNavVisible }) {
<Conversations <Conversations
conversations={convos} conversations={convos}
conversationId={conversationId} conversationId={conversationId}
showMore={showMore} nextPage={nextPage}
previousPage={previousPage}
moveToTop={moveToTop}
pageNumber={pageNumber} pageNumber={pageNumber}
pages={pages}
/> />
)} )}
</div> </div>

View file

@ -13,8 +13,9 @@ const initialState = {
promptPrefix: null, promptPrefix: null,
convosLoading: false, convosLoading: false,
pageNumber: 1, pageNumber: 1,
pages: 1,
refreshConvoHint: 0, refreshConvoHint: 0,
convos: [] convos: [],
}; };
const currentSlice = createSlice({ const currentSlice = createSlice({
@ -30,12 +31,18 @@ const currentSlice = createSlice({
setError: (state, action) => { setError: (state, action) => {
state.error = action.payload; state.error = action.payload;
}, },
incrementPage: (state) => { increasePage: (state) => {
state.pageNumber = state.pageNumber + 1; state.pageNumber = state.pageNumber + 1;
}, },
decreasePage: (state) => {
state.pageNumber = state.pageNumber - 1;
},
setPage: (state, action) => {
state.pageNumber = action.payload;
},
setNewConvo: (state) => { setNewConvo: (state) => {
state.error = false; state.error = false;
state.title = 'New Chat'; state.title = 'ChatGPT Clone';
state.conversationId = null; state.conversationId = null;
state.parentMessageId = null; state.parentMessageId = null;
state.jailbreakConversationId = null; state.jailbreakConversationId = null;
@ -45,13 +52,15 @@ const currentSlice = createSlice({
state.chatGptLabel = null; state.chatGptLabel = null;
state.promptPrefix = null; state.promptPrefix = null;
state.convosLoading = false; state.convosLoading = false;
state.pageNumber = 1;
}, },
setConvos: (state, action) => { setConvos: (state, action) => {
state.convos = action.payload.sort( state.convos = action.payload.sort(
(a, b) => new Date(b.createdAt) - new Date(a.createdAt) (a, b) => new Date(b.createdAt) - new Date(a.createdAt)
); );
}, },
setPages: (state, action) => {
state.pages = action.payload;
},
removeConvo: (state, action) => { removeConvo: (state, action) => {
state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload); state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload);
}, },
@ -61,7 +70,7 @@ const currentSlice = createSlice({
} }
}); });
export const { refreshConversation, setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } = export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll } =
currentSlice.actions; currentSlice.actions;
export default currentSlice.reducer; export default currentSlice.reducer;

View file

@ -1876,3 +1876,6 @@ button.scroll-convo {
background-color:hsla(0,0%,100%,.4) background-color:hsla(0,0%,100%,.4)
} }
} }
.hidden-visibility {
visibility: hidden;
}

View file

@ -9,14 +9,14 @@ const postRequest = async (url, { arg }) => {
return await axios.post(url, { arg }); return await axios.post(url, { arg });
}; };
export const swr = (path, successCallback) => { export const swr = (path, successCallback, options) => {
const options = {}; const _options = {...options};
if (successCallback) { if (successCallback) {
options.onSuccess = successCallback; _options.onSuccess = successCallback;
} }
return useSWR(path, fetcher, options); return useSWR(path, fetcher, _options);
} }
export default function manualSWR(path, type, successCallback) { export default function manualSWR(path, type, successCallback) {