1
Fork 0

Start on infinite scroll pagination

This commit is contained in:
viktorstrate 2021-02-12 17:44:32 +01:00
parent 265025bdf8
commit a9edb148f1
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
8 changed files with 138 additions and 21 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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!

View File

@ -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(

View File

@ -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']),
},
},
}, },
}) })

View File

@ -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={

View File

@ -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