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