1
Fork 0
mirror of https://github.com/diced/zipline.git synced 2025-04-04 23:21:17 -05:00

feat: new system for paged files

This commit is contained in:
diced 2022-12-13 23:32:57 -08:00
parent 27ccbcb54a
commit 3c00575ecd
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
6 changed files with 160 additions and 26 deletions

View file

@ -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 }) {
<SimpleGrid cols={3} spacing='lg' breakpoints={[{ maxWidth: 'sm', cols: 1, spacing: 'sm' }]}>
{pages.isSuccess
? pages.data.length
? pages.data[page - 1 ?? 0].map((image) => (
? pages.data.map((image) => (
<div key={image.id}>
<File image={image} disableMediaPreview={disableMediaPreview} exifEnabled={exifEnabled} />
</div>
@ -65,7 +88,7 @@ export default function FilePagation({ disableMediaPreview, exifEnabled }) {
}}
>
<div></div>
<Pagination total={pages.data?.length ?? 0} page={page} onChange={setPage} />
<Pagination total={numPages} page={page} onChange={setPage} />
<Checkbox
label='Show non-media files'
checked={checked}

View file

@ -1,19 +1,25 @@
import { Accordion, ActionIcon, Box, Group, Pagination, SimpleGrid, Title } from '@mantine/core';
import File from 'components/File';
import { PlusIcon } from 'components/icons';
import useFetch from 'hooks/useFetch';
import { usePaginatedFiles } from 'lib/queries/files';
import Link from 'next/link';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import FilePagation from './FilePagation';
export default function Files({ disableMediaPreview, exifEnabled }) {
const pages = usePaginatedFiles({ filter: 'media' });
const favoritePages = usePaginatedFiles({ favorite: 'media' });
export default function Files({ disableMediaPreview, exifEnabled, queryPage }) {
const [favoritePage, setFavoritePage] = useState(1);
const [favoriteNumPages, setFavoriteNumPages] = useState(0);
const favoritePages = usePaginatedFiles(favoritePage, 'media', true);
useEffect(() => {
(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 }) {
<Accordion.Panel>
<SimpleGrid cols={3} spacing='lg' breakpoints={[{ maxWidth: 'sm', cols: 1, spacing: 'sm' }]}>
{favoritePages.isSuccess && favoritePages.data.length
? favoritePages.data[favoritePage - 1 ?? 0].map((image) => (
? favoritePages.data.map((image) => (
<div key={image.id}>
<File
image={image}
@ -63,18 +69,18 @@ export default function Files({ disableMediaPreview, exifEnabled }) {
paddingBottom: 3,
}}
>
<Pagination
total={favoritePages.data.length}
page={favoritePage}
onChange={setFavoritePage}
/>
<Pagination total={favoriteNumPages} page={favoritePage} onChange={setFavoritePage} />
</Box>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
) : null}
<FilePagation disableMediaPreview={disableMediaPreview} exifEnabled={exifEnabled} />
<FilePagation
disableMediaPreview={disableMediaPreview}
exifEnabled={exifEnabled}
queryPage={queryPage}
/>
</>
);
}

View file

@ -21,6 +21,7 @@ export type ServerSideProps = {
totp_enabled: boolean;
exif_enabled: boolean;
fileId?: string;
queryPage?: string;
};
export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (ctx) => {
@ -71,5 +72,9 @@ export const getServerSideProps: GetServerSideProps<ServerSideProps> = 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;
};

View file

@ -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<typeof useQuery> & {
data: UserFilesResponse[][];
};
return data;
return useQuery<UserFilesResponse[]>(['files', queryString], async () => {
return fetch('/api/user/paged?' + queryString)
.then((res) => res.json() as Promise<UserFilesResponse[]>)
.then((data) => data.map((x) => ({ ...x, created_at: new Date(x.created_at).toLocaleString() })));
});
};
export const useRecent = (filter?: string) => {

View file

@ -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,
});

View file

@ -18,7 +18,11 @@ export default function FilesPage(props) {
</Head>
<Layout props={props}>
<Files disableMediaPreview={props.disable_media_preview} exifEnabled={props.exif_enabled} />
<Files
disableMediaPreview={props.disable_media_preview}
exifEnabled={props.exif_enabled}
queryPage={props.queryPage}
/>
</Layout>
</>
);