improved pagination/loading

This commit is contained in:
Will Webberley 2021-06-02 13:43:23 +01:00
parent 218fb1de74
commit e2a554f1d6
6 changed files with 104 additions and 41 deletions

View File

@ -145,7 +145,6 @@ function Header() {
const uploadRequestResponse = await fetch(`/api/upload?name=${file.name}&type=${file.type}`, {
headers: { Authorization: authToken },
});
setIsUploading(true);
if (uploadRequestResponse.ok) {
const responseJson = await uploadRequestResponse.json();
@ -176,15 +175,16 @@ function Header() {
}
}
}
setIsUploading(false);
}
const handleFileChosen = (event) => {
const handleFileChosen = async (event) => {
const files = event.target?.files;
if (files?.length > 0) {
setIsUploading(true);
for (let i = 0; i < files.length; i++) {
uploadFile(files[i]);
await uploadFile(files[i]);
}
setIsUploading(false);
}
}

View File

@ -25,5 +25,9 @@ const useStore = create(set => ({
photos.splice(0, 0, photo);
return { photos };
}),
nextPhoto: null,
setNextPhoto: (nextPhoto) => set(state => ({ nextPhoto })),
endReached: false,
setEndReached: (endReached) => set(state => ({ endReached })),
}));
export default useStore;

View File

@ -21,6 +21,7 @@
"next": "10.1.3",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-infinite-scroll-component": "^6.1.0",
"styled-components": "^5.3.0",
"uuid": "^8.3.2",
"zustand": "^3.4.1"

View File

@ -31,24 +31,40 @@ export default async (req, res) => {
if (!audience) return;
if (req.method === 'GET') {
const itemsPerPage = 18;
const params = req.query;
const next = req.query && parseInt(req.query.next);
const client = new faunadb.Client({ secret: process.env.FAUNA_SECRET })
var helper = await client.paginate(
q.Match(
q.Index('all_photos')
const helper = await client.query(
q.Paginate(
q.Match(
q.Index("reversed_photos")
),
{
size: itemsPerPage,
after: next ? [next] : undefined,
}
)
);
const photos = [];
await helper.map(ref => {
return q.Get(ref)
}).each(page => {
page.filter(photo => photo.data.fileName && (audience === 'private' || photo.data.audience === 'public')).forEach(photo => {
photos.push(photo);
});
});
const baseUrl = `https://${process.env.S3_BUCKET}.eu-central-1.linodeobjects.com/`;
const fileNames = photos.map(photo => ({ firstName: photo.data.firstName, src: `${baseUrl}${photo.data.fileName}`, originalSrc: photo.data.originalFileName ? `${baseUrl}${photo.data.originalFileName}` : null, thumbSrc: photo.data.thumbFileName ? `${baseUrl}${photo.data.thumbFileName}` : null, blurHash: photo.data.blurHash, isImage: photo.data.isImage, isVideo: photo.data.isVideo }));
res.status(200).json({ photos: fileNames.reverse() });
const photos = helper.data.map(photo => ({
ts: photo[0],
audience: photo[1],
firstName: photo[2],
fileName: photo[3],
src: `${baseUrl}${photo[3]}`,
origianlFileName: photo[4],
originalSrc: photo[4] ? `${baseUrl}${photo[4]}` : null,
thumbFileName: photo[5],
thumbSrc: photo[5] ? `${baseUrl}${photo[5]}` : null,
blurHash: photo[6],
isImage: photo[7],
isVideo: photo[8],
}));
res.status(200).json({ photos, next: helper.after[0], end: !helper.after});
}
if (req.method == 'POST') {

View File

@ -1,34 +1,37 @@
import React, { useEffect } from 'react';
import { Text, Grid, GridItem, Box, AspectRatio, Progress, CircularProgress } from '@chakra-ui/react';
import InfiniteScroll from 'react-infinite-scroll-component';
import Layout from '../lib/Layout';
import LoginPage from '../lib/Login';
import BlurrableImage from '../lib/BlurrableImage';
import useStore from '../lib/store';
export default function Home({ }) {
const { firstName, setFirstName, loggedIn, setLoggedIn, authToken, setAuthToken, isUploading, uploadingCount, selectedPhotoIndex, setSelectedPhotoIndex, photos, setPhotos, addPhoto } = useStore();
const { firstName, setFirstName, loggedIn, setLoggedIn, authToken, setAuthToken, isUploading, uploadingCount, selectedPhotoIndex, setSelectedPhotoIndex, photos, setPhotos, addPhoto, nextPhoto, setNextPhoto, endReached, setEndReached } = useStore();
const getPhotos = async () => {
const getPhotos = async (refresh) => {
if (loggedIn && authToken) {
const response = await fetch('/api/photos', {
const response = await fetch(`/api/photos${(!refresh && nextPhoto) ? `?next=${nextPhoto}` : ''}`, {
headers: {
Authorization: authToken,
},
});
const json = await response.json();
setPhotos(json.photos);
setPhotos(refresh ? json.photos : photos.concat(json.photos));
if (json.next) setNextPhoto(json.next);
setEndReached(json.end);
}
}
// One-time function: load photo URLs from local storage on app-load
useEffect(() => {
const photoString = window.localStorage.getItem('photos');
/*const photoString = window.localStorage.getItem('photos');
if (photoString) {
const unencodedString = JSON.parse(photoString);
if (unencodedString?.photos) {
setPhotos(unencodedString.photos);
}
}
}*/
const storedAuthToken = window.localStorage.getItem('authToken');
if (storedAuthToken) {
setLoggedIn(true);
@ -40,10 +43,20 @@ export default function Home({ }) {
}
}, []);
const fetchData = () => {
getPhotos();
}
const refresh = () => {
setNextPhoto(null);
getPhotos(true);
}
// Every time the photos list changes, save it to local storage
useEffect(() => {
window.localStorage.setItem('photos', JSON.stringify({ photos }));
}, [photos]);
/*useEffect(() => {
if (photos?.length > 0)
window.localStorage.setItem('photos', JSON.stringify({ photos: photos.slice(0, 20) }));
}, [photos]);*/
useEffect(() => {
window.localStorage.setItem('authToken', authToken);
@ -65,20 +78,37 @@ export default function Home({ }) {
{isUploading &&
<Progress size="sm" isIndeterminate colorScheme='blue' />
}
<Grid templateColumns="repeat(3, 1fr)" gap={1} flex={1}>
{uploadingCount > 0 && [...Array(uploadingCount)].map((e, i) =>
<AspectRatio maxW="100%" ratio={1}>
<Box>
<CircularProgress isIndeterminate colorScheme="green" />
</Box>
</AspectRatio>
)}
{photos.map((photo, i) =>
<AspectRatio maxW="100%" ratio={1} key={photo.src}>
<BlurrableImage {...photo} onClick={e => setSelectedPhotoIndex(i)}/>
</AspectRatio>
)}
</Grid>
{/*
<Grid templateColumns="repeat(3, 1fr)" gap={0.5} flex={1}>*/}
<InfiniteScroll
dataLength={photos.length} //This is important field to render the next data
next={fetchData}
hasMore={!endReached}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
// below props only if you need pull down functionality
refreshFunction={refresh}
pullDownToRefresh={false}
pullDownToRefreshThreshold={50}
pullDownToRefreshContent={
<h3 style={{ textAlign: 'center' }}>&#8595; Pull down to refresh</h3>
}
releaseToRefreshContent={
<h3 style={{ textAlign: 'center' }}>&#8593; Release to refresh</h3>
}
style={{display: 'grid', gridGap: 2, gridTemplateColumns: 'repeat(3, 1fr)'}}
>
{photos.map((photo, i) =>
<AspectRatio maxW="100%" ratio={1} key={photo.src}>
<BlurrableImage {...photo} onClick={e => setSelectedPhotoIndex(i)}/>
</AspectRatio>
)}
</InfiniteScroll>
{/*</Grid>*/}
{selectedPhotoIndex > -1 &&
<div style={{position: 'fixed', top: 0, width: '100%', height: '100%', background: 'rgba(0,0,0,0.8)', display: 'flex', flexDirection: 'column', justifyContent: 'center'}} onClick={e => setSelectedPhotoIndex(-1)}>

View File

@ -3499,6 +3499,13 @@ react-focus-lock@2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
react-infinite-scroll-component@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f"
integrity sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==
dependencies:
throttle-debounce "^2.1.0"
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -3862,6 +3869,11 @@ supports-color@^8.0.0:
dependencies:
has-flag "^4.0.0"
throttle-debounce@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==
timers-browserify@2.0.12, timers-browserify@^2.0.4:
version "2.0.12"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee"