improved pagination/loading
This commit is contained in:
parent
218fb1de74
commit
e2a554f1d6
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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') {
|
||||
|
@ -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' }}>↓ Pull down to refresh</h3>
|
||||
}
|
||||
releaseToRefreshContent={
|
||||
<h3 style={{ textAlign: 'center' }}>↑ 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)}>
|
||||
|
12
yarn.lock
12
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user