Start on infinite scroll pagination
This commit is contained in:
parent
265025bdf8
commit
a9edb148f1
|
@ -152,7 +152,7 @@ type ComplexityRoot struct {
|
||||||
MyAlbums func(childComplexity int, filter *models.Filter, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) int
|
MyAlbums func(childComplexity int, filter *models.Filter, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) int
|
||||||
MyMedia func(childComplexity int, filter *models.Filter) int
|
MyMedia func(childComplexity int, filter *models.Filter) int
|
||||||
MyMediaGeoJSON func(childComplexity int) int
|
MyMediaGeoJSON func(childComplexity int) int
|
||||||
MyTimeline func(childComplexity int, onlyFavorites *bool) int
|
MyTimeline func(childComplexity int, limit *int, offset *int, onlyFavorites *bool) int
|
||||||
MyUser func(childComplexity int) int
|
MyUser func(childComplexity int) int
|
||||||
Search func(childComplexity int, query string, limitMedia *int, limitAlbums *int) int
|
Search func(childComplexity int, query string, limitMedia *int, limitAlbums *int) int
|
||||||
ShareToken func(childComplexity int, token string, password *string) int
|
ShareToken func(childComplexity int, token string, password *string) int
|
||||||
|
@ -272,7 +272,7 @@ type QueryResolver interface {
|
||||||
MyMedia(ctx context.Context, filter *models.Filter) ([]*models.Media, error)
|
MyMedia(ctx context.Context, filter *models.Filter) ([]*models.Media, error)
|
||||||
Media(ctx context.Context, id int) (*models.Media, error)
|
Media(ctx context.Context, id int) (*models.Media, error)
|
||||||
MediaList(ctx context.Context, ids []int) ([]*models.Media, error)
|
MediaList(ctx context.Context, ids []int) ([]*models.Media, error)
|
||||||
MyTimeline(ctx context.Context, onlyFavorites *bool) ([]*models.TimelineGroup, error)
|
MyTimeline(ctx context.Context, limit *int, offset *int, onlyFavorites *bool) ([]*models.TimelineGroup, error)
|
||||||
MyMediaGeoJSON(ctx context.Context) (interface{}, error)
|
MyMediaGeoJSON(ctx context.Context) (interface{}, error)
|
||||||
MapboxToken(ctx context.Context) (*string, error)
|
MapboxToken(ctx context.Context) (*string, error)
|
||||||
ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
||||||
|
@ -950,7 +950,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Query.MyTimeline(childComplexity, args["onlyFavorites"].(*bool)), true
|
return e.complexity.Query.MyTimeline(childComplexity, args["limit"].(*int), args["offset"].(*int), args["onlyFavorites"].(*bool)), true
|
||||||
|
|
||||||
case "Query.myUser":
|
case "Query.myUser":
|
||||||
if e.complexity.Query.MyUser == nil {
|
if e.complexity.Query.MyUser == nil {
|
||||||
|
@ -1400,7 +1400,7 @@ type Query {
|
||||||
"Get a list of media by their ids, user must own the media or be admin"
|
"Get a list of media by their ids, user must own the media or be admin"
|
||||||
mediaList(ids: [ID!]!): [Media!]!
|
mediaList(ids: [ID!]!): [Media!]!
|
||||||
|
|
||||||
myTimeline(onlyFavorites: Boolean): [TimelineGroup!]!
|
myTimeline(limit: Int, offset: Int, onlyFavorites: Boolean): [TimelineGroup!]!
|
||||||
|
|
||||||
"Get media owned by the logged in user, returned in GeoJson format"
|
"Get media owned by the logged in user, returned in GeoJson format"
|
||||||
myMediaGeoJson: Any!
|
myMediaGeoJson: Any!
|
||||||
|
@ -2195,15 +2195,33 @@ func (ec *executionContext) field_Query_myMedia_args(ctx context.Context, rawArg
|
||||||
func (ec *executionContext) field_Query_myTimeline_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
func (ec *executionContext) field_Query_myTimeline_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]interface{}{}
|
args := map[string]interface{}{}
|
||||||
var arg0 *bool
|
var arg0 *int
|
||||||
if tmp, ok := rawArgs["onlyFavorites"]; ok {
|
if tmp, ok := rawArgs["limit"]; ok {
|
||||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("onlyFavorites"))
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("limit"))
|
||||||
arg0, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
|
arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args["onlyFavorites"] = arg0
|
args["limit"] = arg0
|
||||||
|
var arg1 *int
|
||||||
|
if tmp, ok := rawArgs["offset"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("offset"))
|
||||||
|
arg1, err = ec.unmarshalOInt2ᚖint(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["offset"] = arg1
|
||||||
|
var arg2 *bool
|
||||||
|
if tmp, ok := rawArgs["onlyFavorites"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("onlyFavorites"))
|
||||||
|
arg2, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["onlyFavorites"] = arg2
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5231,7 +5249,7 @@ func (ec *executionContext) _Query_myTimeline(ctx context.Context, field graphql
|
||||||
fc.Args = args
|
fc.Args = args
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
ctx = rctx // use context from middleware stack in children
|
||||||
return ec.resolvers.Query().MyTimeline(rctx, args["onlyFavorites"].(*bool))
|
return ec.resolvers.Query().MyTimeline(rctx, args["limit"].(*int), args["offset"].(*int), args["onlyFavorites"].(*bool))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) MyTimeline(ctx context.Context, onlyFavorites *bool) ([]*models.TimelineGroup, error) {
|
func (r *queryResolver) MyTimeline(ctx context.Context, limit *int, offset *int, onlyFavorites *bool) ([]*models.TimelineGroup, error) {
|
||||||
user := auth.UserFromContext(ctx)
|
user := auth.UserFromContext(ctx)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, auth.ErrUnauthorized
|
return nil, auth.ErrUnauthorized
|
||||||
|
@ -33,6 +33,14 @@ func (r *queryResolver) MyTimeline(ctx context.Context, onlyFavorites *bool) ([]
|
||||||
daysQuery.Where("media.id IN (?)", tx.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1"))
|
daysQuery.Where("media.id IN (?)", tx.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if limit != nil {
|
||||||
|
daysQuery.Limit(*limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset != nil {
|
||||||
|
daysQuery.Offset(*offset)
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := daysQuery.Group("albums.id, YEAR(media.date_shot), MONTH(media.date_shot), DAY(media.date_shot)").
|
rows, err := daysQuery.Group("albums.id, YEAR(media.date_shot), MONTH(media.date_shot), DAY(media.date_shot)").
|
||||||
Order("media.date_shot DESC").
|
Order("media.date_shot DESC").
|
||||||
Rows()
|
Rows()
|
||||||
|
|
|
@ -44,7 +44,7 @@ type Query {
|
||||||
"Get a list of media by their ids, user must own the media or be admin"
|
"Get a list of media by their ids, user must own the media or be admin"
|
||||||
mediaList(ids: [ID!]!): [Media!]!
|
mediaList(ids: [ID!]!): [Media!]!
|
||||||
|
|
||||||
myTimeline(onlyFavorites: Boolean): [TimelineGroup!]!
|
myTimeline(limit: Int, offset: Int, onlyFavorites: Boolean): [TimelineGroup!]!
|
||||||
|
|
||||||
"Get media owned by the logged in user, returned in GeoJson format"
|
"Get media owned by the logged in user, returned in GeoJson format"
|
||||||
myMediaGeoJson: Any!
|
myMediaGeoJson: Any!
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useQuery, gql } from '@apollo/client'
|
||||||
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
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 '../../components/useURLParameters'
|
import useURLParameters from '../../hooks/useURLParameters'
|
||||||
|
|
||||||
const albumQuery = gql`
|
const albumQuery = gql`
|
||||||
query albumQuery(
|
query albumQuery(
|
||||||
|
|
|
@ -5,7 +5,10 @@ import {
|
||||||
ApolloLink,
|
ApolloLink,
|
||||||
HttpLink,
|
HttpLink,
|
||||||
} from '@apollo/client'
|
} from '@apollo/client'
|
||||||
import { getMainDefinition } from '@apollo/client/utilities'
|
import {
|
||||||
|
getMainDefinition,
|
||||||
|
offsetLimitPagination,
|
||||||
|
} from '@apollo/client/utilities'
|
||||||
import { onError } from '@apollo/client/link/error'
|
import { onError } from '@apollo/client/link/error'
|
||||||
import { WebSocketLink } from '@apollo/client/link/ws'
|
import { WebSocketLink } from '@apollo/client/link/ws'
|
||||||
|
|
||||||
|
@ -111,6 +114,11 @@ const memoryCache = new InMemoryCache({
|
||||||
SiteInfo: {
|
SiteInfo: {
|
||||||
merge: true,
|
merge: true,
|
||||||
},
|
},
|
||||||
|
Query: {
|
||||||
|
fields: {
|
||||||
|
myTimeline: offsetLimitPagination(['onlyFavorites']),
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,13 @@ import TimelineGroupDate from './TimelineGroupDate'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import PresentView from '../photoGallery/presentView/PresentView'
|
import PresentView from '../photoGallery/presentView/PresentView'
|
||||||
import { Loader } from 'semantic-ui-react'
|
import { Loader } from 'semantic-ui-react'
|
||||||
import useURLParameters from '../useURLParameters'
|
import useURLParameters from '../../hooks/useURLParameters'
|
||||||
import { FavoritesCheckbox } from '../AlbumFilter'
|
import { FavoritesCheckbox } from '../AlbumFilter'
|
||||||
|
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||||
|
|
||||||
const MY_TIMELINE_QUERY = gql`
|
const MY_TIMELINE_QUERY = gql`
|
||||||
query myTimeline($onlyFavorites: Boolean) {
|
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) {
|
||||||
myTimeline(onlyFavorites: $onlyFavorites) {
|
myTimeline(onlyFavorites: $onlyFavorites, limit: $limit, offset: $offset) {
|
||||||
album {
|
album {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
@ -123,9 +124,26 @@ const TimelineGallery = () => {
|
||||||
})
|
})
|
||||||
}, [activeIndex])
|
}, [activeIndex])
|
||||||
|
|
||||||
const { data, error, loading, refetch } = useQuery(MY_TIMELINE_QUERY, {
|
const { data, error, loading, refetch, fetchMore } = useQuery(
|
||||||
variables: {
|
MY_TIMELINE_QUERY,
|
||||||
onlyFavorites,
|
{
|
||||||
|
variables: {
|
||||||
|
onlyFavorites,
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const { containerElem } = useScrollPagination({
|
||||||
|
loading,
|
||||||
|
fetchMore: () => {
|
||||||
|
console.log('fetching more')
|
||||||
|
fetchMore({
|
||||||
|
variables: {
|
||||||
|
offset: data.myTimeline.length,
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -190,7 +208,7 @@ const TimelineGallery = () => {
|
||||||
onlyFavorites={onlyFavorites}
|
onlyFavorites={onlyFavorites}
|
||||||
setOnlyFavorites={setOnlyFavorites}
|
setOnlyFavorites={setOnlyFavorites}
|
||||||
/>
|
/>
|
||||||
<GalleryWrapper>{timelineGroups}</GalleryWrapper>
|
<GalleryWrapper ref={containerElem}>{timelineGroups}</GalleryWrapper>
|
||||||
{presenting && (
|
{presenting && (
|
||||||
<PresentView
|
<PresentView
|
||||||
media={
|
media={
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
const useScrollPagination = ({ loading, fetchMore }) => {
|
||||||
|
const observer = useRef(null)
|
||||||
|
const observerElem = useRef(null)
|
||||||
|
|
||||||
|
const reconfigureIntersectionObserver = () => {
|
||||||
|
var options = {
|
||||||
|
root: null,
|
||||||
|
rootMargin: '-100% 0px 0px 0px',
|
||||||
|
threshold: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old observer
|
||||||
|
if (observer.current) observer.current.disconnect()
|
||||||
|
|
||||||
|
// configure new observer
|
||||||
|
observer.current = new IntersectionObserver(entities => {
|
||||||
|
console.log('Observing', entities)
|
||||||
|
if (entities.find(x => x.isIntersecting == false)) {
|
||||||
|
console.log('load more')
|
||||||
|
fetchMore()
|
||||||
|
}
|
||||||
|
}, options)
|
||||||
|
|
||||||
|
// activate new observer
|
||||||
|
if (observerElem.current && !loading)
|
||||||
|
observer.current.observe(observerElem.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerElem = useCallback(node => {
|
||||||
|
observerElem.current = node
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
if (observer.current != null) {
|
||||||
|
observer.current.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node != null) {
|
||||||
|
reconfigureIntersectionObserver()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// only observe when not loading
|
||||||
|
useEffect(() => {
|
||||||
|
if (observer.current != null) {
|
||||||
|
if (loading) {
|
||||||
|
observer.current.unobserve(observerElem.current)
|
||||||
|
} else {
|
||||||
|
observer.current.observe(observerElem.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [loading])
|
||||||
|
|
||||||
|
// reconfigure observer if fetchMore function changes
|
||||||
|
useEffect(() => {
|
||||||
|
reconfigureIntersectionObserver()
|
||||||
|
}, [fetchMore])
|
||||||
|
|
||||||
|
return {
|
||||||
|
containerElem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useScrollPagination
|
Loading…
Reference in New Issue