diff --git a/src/components/pages/Files/FilePagation.tsx b/src/components/pages/Files/FilePagation.tsx index 5ad894c3..5403ff1e 100644 --- a/src/components/pages/Files/FilePagation.tsx +++ b/src/components/pages/Files/FilePagation.tsx @@ -2,16 +2,39 @@ import { Box, Button, Center, Checkbox, Group, Pagination, SimpleGrid, Skeleton, import File from 'components/File'; import { FileIcon } from 'components/icons'; import MutedText from 'components/MutedText'; +import useFetch from 'hooks/useFetch'; import { usePaginatedFiles } from 'lib/queries/files'; import { showNonMediaSelector } from 'lib/recoil/settings'; -import { useState } from 'react'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; import { useRecoilState } from 'recoil'; -export default function FilePagation({ disableMediaPreview, exifEnabled }) { +export default function FilePagation({ disableMediaPreview, exifEnabled, queryPage }) { const [checked, setChecked] = useRecoilState(showNonMediaSelector); + const [numPages, setNumPages] = useState(Number(queryPage)); // just set it to the queryPage, since the req may have not loaded yet + const [page, setPage] = useState(Number(queryPage)); - const pages = usePaginatedFiles(!checked ? { filter: 'media' } : {}); - const [page, setPage] = useState(1); + const router = useRouter(); + + useEffect(() => { + (async () => { + router.replace( + { + query: { + ...router.query, + page: page, + }, + }, + undefined, + { shallow: true } + ); + + const { count } = await useFetch(`/api/user/paged?type=count${!checked ? '&filter=media' : ''}`); + setNumPages(count); + })(); + }, [page]); + + const pages = usePaginatedFiles(page, !checked ? 'media' : null); if (pages.isSuccess && pages.data.length === 0) { return ( @@ -42,7 +65,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled }) { {pages.isSuccess ? pages.data.length - ? pages.data[page - 1 ?? 0].map((image) => ( + ? pages.data.map((image) => (
@@ -65,7 +88,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled }) { }} >
- + { + (async () => { + const { count } = await useFetch('/api/user/paged?type=count&filter=media&favorite=true'); + setFavoriteNumPages(count); + })(); + }); const updatePages = async (favorite) => { - pages.refetch(); - if (favorite) { favoritePages.refetch(); } @@ -43,7 +49,7 @@ export default function Files({ disableMediaPreview, exifEnabled }) { {favoritePages.isSuccess && favoritePages.data.length - ? favoritePages.data[favoritePage - 1 ?? 0].map((image) => ( + ? favoritePages.data.map((image) => (
- + ) : null} - + ); } diff --git a/src/lib/middleware/getServerSideProps.ts b/src/lib/middleware/getServerSideProps.ts index 77eb8b04..1c06ef6e 100644 --- a/src/lib/middleware/getServerSideProps.ts +++ b/src/lib/middleware/getServerSideProps.ts @@ -21,6 +21,7 @@ export type ServerSideProps = { totp_enabled: boolean; exif_enabled: boolean; fileId?: string; + queryPage?: string; }; export const getServerSideProps: GetServerSideProps = async (ctx) => { @@ -71,5 +72,9 @@ export const getServerSideProps: GetServerSideProps = async (ct obj.props.fileId = ctx.query.id as string; } + if (ctx.resolvedUrl.startsWith('/dashboard/files')) { + obj.props.queryPage = (ctx.query.page as string) || '1'; + } + return obj; }; diff --git a/src/lib/queries/files.ts b/src/lib/queries/files.ts index 8cff43cb..66ab9382 100644 --- a/src/lib/queries/files.ts +++ b/src/lib/queries/files.ts @@ -28,13 +28,19 @@ export const useFiles = (query: { [key: string]: string } = {}) => { ); }); }; +export const usePaginatedFiles = (page?: number, filter: string = 'media', favorite = null) => { + const queryBuilder = new URLSearchParams({ + page: Number(page || '1').toString(), + filter, + ...(favorite !== null && { favorite: favorite.toString() }), + }); + const queryString = queryBuilder.toString(); -export const usePaginatedFiles = (query: { [key: string]: string } = {}) => { - query['paged'] = 'true'; - const data = useFiles(query) as ReturnType & { - data: UserFilesResponse[][]; - }; - return data; + return useQuery(['files', queryString], async () => { + return fetch('/api/user/paged?' + queryString) + .then((res) => res.json() as Promise) + .then((data) => data.map((x) => ({ ...x, created_at: new Date(x.created_at).toLocaleString() }))); + }); }; export const useRecent = (filter?: string) => { diff --git a/src/pages/api/user/paged.ts b/src/pages/api/user/paged.ts new file mode 100644 index 00000000..b23b43ed --- /dev/null +++ b/src/pages/api/user/paged.ts @@ -0,0 +1,90 @@ +import config from 'lib/config'; +import prisma from 'lib/prisma'; +import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline'; + +const pageCount = 16; + +async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) { + const { page, filter, type, favorite } = req.query as { + page: string; + filter: string; + type: string; + favorite: string; + }; + + const where = { + userId: user.id, + favorite: !!favorite, + + ...(filter === 'media' && { + OR: [ + { + mimetype: { startsWith: 'image/' }, + }, + { + mimetype: { startsWith: 'video/' }, + }, + { + mimetype: { startsWith: 'audio/' }, + }, + { + mimetype: { startsWith: 'text/' }, + }, + ], + }), + }; + + if (type === 'count') { + const count = await prisma.image.count({ + where, + }); + + const pages = Math.ceil(count / pageCount); + + return res.json({ count: pages }); + } + + if (!page) return res.badRequest('no page'); + if (isNaN(Number(page))) return res.badRequest('page is not a number'); + + let files: { + favorite: boolean; + created_at: Date; + id: number; + file: string; + mimetype: string; + expires_at: Date; + maxViews: number; + views: number; + }[] = await prisma.image.findMany({ + where, + orderBy: { + created_at: 'desc', + }, + select: { + created_at: true, + expires_at: true, + file: true, + mimetype: true, + id: true, + favorite: true, + views: true, + maxViews: true, + }, + skip: page ? (Number(page) - 1) * pageCount : undefined, + take: page ? pageCount : undefined, + }); + + for (let i = 0; i !== files.length; ++i) { + (files[i] as unknown as { url: string }).url = `${ + config.uploader.route === '/' ? '' : `${config.uploader.route}/` + }${files[i].file}`; + } + + return res.json(files); +} + +export default withZipline(handler, { + methods: ['GET'], + user: true, +}); diff --git a/src/pages/dashboard/files.tsx b/src/pages/dashboard/files.tsx index 199f4a9d..bbb7d757 100644 --- a/src/pages/dashboard/files.tsx +++ b/src/pages/dashboard/files.tsx @@ -18,7 +18,11 @@ export default function FilesPage(props) { - + );