Add pagination for albums + add loading indicators
This commit is contained in:
parent
a9edb148f1
commit
1dbf4e4821
|
@ -5,6 +5,8 @@ import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
|||
import PropTypes from 'prop-types'
|
||||
import Layout from '../../Layout'
|
||||
import useURLParameters from '../../hooks/useURLParameters'
|
||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||
import { Loader } from 'semantic-ui-react'
|
||||
|
||||
const albumQuery = gql`
|
||||
query albumQuery(
|
||||
|
@ -12,6 +14,8 @@ const albumQuery = gql`
|
|||
$onlyFavorites: Boolean
|
||||
$mediaOrderBy: String
|
||||
$mediaOrderDirection: OrderDirection
|
||||
$limit: Int
|
||||
$offset: Int
|
||||
) {
|
||||
album(id: $id) {
|
||||
id
|
||||
|
@ -27,6 +31,8 @@ const albumQuery = gql`
|
|||
}
|
||||
media(
|
||||
filter: {
|
||||
limit: $limit
|
||||
offset: $offset
|
||||
order_by: $mediaOrderBy
|
||||
order_direction: $mediaOrderDirection
|
||||
}
|
||||
|
@ -80,15 +86,24 @@ function AlbumPage({ match }) {
|
|||
[setParams]
|
||||
)
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(albumQuery, {
|
||||
const { loading, error, data, refetch, fetchMore } = useQuery(albumQuery, {
|
||||
variables: {
|
||||
id: albumId,
|
||||
onlyFavorites,
|
||||
mediaOrderBy: orderBy,
|
||||
mediaOrderDirection: orderDirection,
|
||||
offset: 0,
|
||||
limit: 200,
|
||||
},
|
||||
})
|
||||
|
||||
const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
|
||||
loading,
|
||||
fetchMore,
|
||||
data,
|
||||
getItems: data => data.album.media,
|
||||
})
|
||||
|
||||
const toggleFavorites = useCallback(
|
||||
onlyFavorites => {
|
||||
if (
|
||||
|
@ -115,6 +130,7 @@ function AlbumPage({ match }) {
|
|||
return (
|
||||
<Layout title={data ? data.album.title : 'Loading album'}>
|
||||
<AlbumGallery
|
||||
ref={containerElem}
|
||||
album={data && data.album}
|
||||
loading={loading}
|
||||
showFavoritesToggle
|
||||
|
@ -125,6 +141,13 @@ function AlbumPage({ match }) {
|
|||
setOrdering={setOrdering}
|
||||
ordering={{ orderBy, orderDirection }}
|
||||
/>
|
||||
<Loader
|
||||
style={{ margin: '42px 0 24px 0' }}
|
||||
active={!finishedLoadingMore && !loading}
|
||||
inline="centered"
|
||||
>
|
||||
Loading more media
|
||||
</Loader>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -114,6 +114,20 @@ const memoryCache = new InMemoryCache({
|
|||
SiteInfo: {
|
||||
merge: true,
|
||||
},
|
||||
MediaURL: {
|
||||
keyFields: ['url'],
|
||||
},
|
||||
Album: {
|
||||
fields: {
|
||||
media: {
|
||||
keyArgs: ['onlyFavorites'],
|
||||
merge(existing = [], incoming) {
|
||||
console.log('merge media', existing, incoming)
|
||||
return [...existing, ...incoming]
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
fields: {
|
||||
myTimeline: offsetLimitPagination(['onlyFavorites']),
|
||||
|
|
|
@ -5,124 +5,129 @@ import PhotoGallery from '../photoGallery/PhotoGallery'
|
|||
import AlbumBoxes from './AlbumBoxes'
|
||||
import AlbumFilter from '../AlbumFilter'
|
||||
|
||||
const AlbumGallery = ({
|
||||
album,
|
||||
loading = false,
|
||||
customAlbumLink,
|
||||
showFilter = false,
|
||||
setOnlyFavorites,
|
||||
setOrdering,
|
||||
ordering,
|
||||
onlyFavorites = false,
|
||||
onFavorite,
|
||||
}) => {
|
||||
const [imageState, setImageState] = useState({
|
||||
activeImage: -1,
|
||||
presenting: false,
|
||||
})
|
||||
const AlbumGallery = React.forwardRef(
|
||||
(
|
||||
{
|
||||
album,
|
||||
loading = false,
|
||||
customAlbumLink,
|
||||
showFilter = false,
|
||||
setOnlyFavorites,
|
||||
setOrdering,
|
||||
ordering,
|
||||
onlyFavorites = false,
|
||||
onFavorite,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [imageState, setImageState] = useState({
|
||||
activeImage: -1,
|
||||
presenting: false,
|
||||
})
|
||||
|
||||
const setPresenting = presenting =>
|
||||
setImageState(state => ({ ...state, presenting }))
|
||||
const setPresenting = presenting =>
|
||||
setImageState(state => ({ ...state, presenting }))
|
||||
|
||||
const setPresentingWithHistory = presenting => {
|
||||
setPresenting(presenting)
|
||||
if (presenting) {
|
||||
history.pushState({ imageState }, '')
|
||||
} else {
|
||||
history.back()
|
||||
}
|
||||
}
|
||||
|
||||
const updateHistory = imageState => {
|
||||
history.replaceState({ imageState }, '')
|
||||
return imageState
|
||||
}
|
||||
|
||||
const setActiveImage = activeImage => {
|
||||
setImageState(state => updateHistory({ ...state, activeImage }))
|
||||
}
|
||||
|
||||
const nextImage = () => {
|
||||
setActiveImage((imageState.activeImage + 1) % album.media.length)
|
||||
}
|
||||
|
||||
const previousImage = () => {
|
||||
if (imageState.activeImage <= 0) {
|
||||
setActiveImage(album.media.length - 1)
|
||||
} else {
|
||||
setActiveImage(imageState.activeImage - 1)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateImageState = event => {
|
||||
setImageState(event.state.imageState)
|
||||
}
|
||||
window.addEventListener('popstate', updateImageState)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('popstate', updateImageState)
|
||||
}
|
||||
}, [imageState])
|
||||
|
||||
useEffect(() => {
|
||||
setActiveImage(-1)
|
||||
}, [album])
|
||||
|
||||
let subAlbumElement = null
|
||||
|
||||
if (album) {
|
||||
if (album.subAlbums.length > 0) {
|
||||
subAlbumElement = (
|
||||
<AlbumBoxes
|
||||
loading={loading}
|
||||
albums={album.subAlbums}
|
||||
getCustomLink={customAlbumLink}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
subAlbumElement = <AlbumBoxes loading={loading} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AlbumTitle album={album} disableLink />
|
||||
{showFilter && (
|
||||
<AlbumFilter
|
||||
onlyFavorites={onlyFavorites}
|
||||
setOnlyFavorites={setOnlyFavorites}
|
||||
setOrdering={setOrdering}
|
||||
ordering={ordering}
|
||||
/>
|
||||
)}
|
||||
{subAlbumElement}
|
||||
{
|
||||
<h2
|
||||
style={{
|
||||
opacity: loading ? 0 : 1,
|
||||
display: album && album.subAlbums.length > 0 ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
Images
|
||||
</h2>
|
||||
const setPresentingWithHistory = presenting => {
|
||||
setPresenting(presenting)
|
||||
if (presenting) {
|
||||
history.pushState({ imageState }, '')
|
||||
} else {
|
||||
history.back()
|
||||
}
|
||||
<PhotoGallery
|
||||
loading={loading}
|
||||
media={album && album.media}
|
||||
activeIndex={imageState.activeImage}
|
||||
presenting={imageState.presenting}
|
||||
onSelectImage={index => {
|
||||
setActiveImage(index)
|
||||
}}
|
||||
onFavorite={onFavorite}
|
||||
setPresenting={setPresentingWithHistory}
|
||||
nextImage={nextImage}
|
||||
previousImage={previousImage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const updateHistory = imageState => {
|
||||
history.replaceState({ imageState }, '')
|
||||
return imageState
|
||||
}
|
||||
|
||||
const setActiveImage = activeImage => {
|
||||
setImageState(state => updateHistory({ ...state, activeImage }))
|
||||
}
|
||||
|
||||
const nextImage = () => {
|
||||
setActiveImage((imageState.activeImage + 1) % album.media.length)
|
||||
}
|
||||
|
||||
const previousImage = () => {
|
||||
if (imageState.activeImage <= 0) {
|
||||
setActiveImage(album.media.length - 1)
|
||||
} else {
|
||||
setActiveImage(imageState.activeImage - 1)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateImageState = event => {
|
||||
setImageState(event.state.imageState)
|
||||
}
|
||||
window.addEventListener('popstate', updateImageState)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('popstate', updateImageState)
|
||||
}
|
||||
}, [imageState])
|
||||
|
||||
useEffect(() => {
|
||||
setActiveImage(-1)
|
||||
}, [album])
|
||||
|
||||
let subAlbumElement = null
|
||||
|
||||
if (album) {
|
||||
if (album.subAlbums.length > 0) {
|
||||
subAlbumElement = (
|
||||
<AlbumBoxes
|
||||
loading={loading}
|
||||
albums={album.subAlbums}
|
||||
getCustomLink={customAlbumLink}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
subAlbumElement = <AlbumBoxes loading={loading} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<AlbumTitle album={album} disableLink />
|
||||
{showFilter && (
|
||||
<AlbumFilter
|
||||
onlyFavorites={onlyFavorites}
|
||||
setOnlyFavorites={setOnlyFavorites}
|
||||
setOrdering={setOrdering}
|
||||
ordering={ordering}
|
||||
/>
|
||||
)}
|
||||
{subAlbumElement}
|
||||
{
|
||||
<h2
|
||||
style={{
|
||||
opacity: loading ? 0 : 1,
|
||||
display: album && album.subAlbums.length > 0 ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
Images
|
||||
</h2>
|
||||
}
|
||||
<PhotoGallery
|
||||
loading={loading}
|
||||
media={album && album.media}
|
||||
activeIndex={imageState.activeImage}
|
||||
presenting={imageState.presenting}
|
||||
onSelectImage={index => {
|
||||
setActiveImage(index)
|
||||
}}
|
||||
onFavorite={onFavorite}
|
||||
setPresenting={setPresentingWithHistory}
|
||||
nextImage={nextImage}
|
||||
previousImage={previousImage}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
AlbumGallery.propTypes = {
|
||||
album: PropTypes.object,
|
||||
|
|
|
@ -130,21 +130,16 @@ const TimelineGallery = () => {
|
|||
variables: {
|
||||
onlyFavorites,
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
limit: 50,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const { containerElem } = useScrollPagination({
|
||||
const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
|
||||
loading,
|
||||
fetchMore: () => {
|
||||
console.log('fetching more')
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: data.myTimeline.length,
|
||||
},
|
||||
})
|
||||
},
|
||||
fetchMore,
|
||||
data,
|
||||
getItems: data => data.myTimeline,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -209,6 +204,13 @@ const TimelineGallery = () => {
|
|||
setOnlyFavorites={setOnlyFavorites}
|
||||
/>
|
||||
<GalleryWrapper ref={containerElem}>{timelineGroups}</GalleryWrapper>
|
||||
<Loader
|
||||
style={{ margin: '42px 0 24px 0' }}
|
||||
active={!finishedLoadingMore && !loading}
|
||||
inline="centered"
|
||||
>
|
||||
Loading more media
|
||||
</Loader>
|
||||
{presenting && (
|
||||
<PresentView
|
||||
media={
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
const useScrollPagination = ({ loading, fetchMore }) => {
|
||||
const useScrollPagination = ({ loading, fetchMore, data, getItems }) => {
|
||||
const observer = useRef(null)
|
||||
const observerElem = useRef(null)
|
||||
const [finished, setFinished] = useState(false)
|
||||
|
||||
const reconfigureIntersectionObserver = () => {
|
||||
var options = {
|
||||
|
@ -14,18 +15,32 @@ const useScrollPagination = ({ loading, fetchMore }) => {
|
|||
// delete old observer
|
||||
if (observer.current) observer.current.disconnect()
|
||||
|
||||
if (finished) return
|
||||
|
||||
// configure new observer
|
||||
observer.current = new IntersectionObserver(entities => {
|
||||
console.log('Observing', entities)
|
||||
if (entities.find(x => x.isIntersecting == false)) {
|
||||
console.log('load more')
|
||||
fetchMore()
|
||||
let itemCount = getItems(data).length
|
||||
console.log('load more', itemCount)
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: itemCount,
|
||||
},
|
||||
}).then(result => {
|
||||
const newItemCount = getItems(result.data).length
|
||||
console.log('then', result, itemCount, newItemCount)
|
||||
if (newItemCount == 0) {
|
||||
setFinished(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, options)
|
||||
|
||||
// activate new observer
|
||||
if (observerElem.current && !loading)
|
||||
if (observerElem.current && !loading) {
|
||||
observer.current.observe(observerElem.current)
|
||||
}
|
||||
}
|
||||
|
||||
const containerElem = useCallback(node => {
|
||||
|
@ -55,10 +70,11 @@ const useScrollPagination = ({ loading, fetchMore }) => {
|
|||
// reconfigure observer if fetchMore function changes
|
||||
useEffect(() => {
|
||||
reconfigureIntersectionObserver()
|
||||
}, [fetchMore])
|
||||
}, [fetchMore, data, finished])
|
||||
|
||||
return {
|
||||
containerElem,
|
||||
finished,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue