mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 18:19:11 +00:00
Add graphql query for getting media from an array of ids
- Add keyboard navigation when presenting images in map view
This commit is contained in:
@@ -146,6 +146,7 @@ type ComplexityRoot struct {
|
||||
Album func(childComplexity int, id int) int
|
||||
MapboxToken func(childComplexity int) int
|
||||
Media func(childComplexity int, id int) int
|
||||
MediaList func(childComplexity int, ids []int) int
|
||||
MyAlbums func(childComplexity int, filter *models.Filter, onlyRoot *bool, showEmpty *bool, onlyWithFavorites *bool) int
|
||||
MyMedia func(childComplexity int, filter *models.Filter) int
|
||||
MyMediaGeoJSON func(childComplexity int) int
|
||||
@@ -257,6 +258,7 @@ type QueryResolver interface {
|
||||
Album(ctx context.Context, id int) (*models.Album, error)
|
||||
MyMedia(ctx context.Context, filter *models.Filter) ([]*models.Media, error)
|
||||
Media(ctx context.Context, id int) (*models.Media, error)
|
||||
MediaList(ctx context.Context, ids []int) ([]*models.Media, error)
|
||||
MyMediaGeoJSON(ctx context.Context) (interface{}, error)
|
||||
MapboxToken(ctx context.Context) (*string, error)
|
||||
ShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
||||
@@ -869,6 +871,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Query.Media(childComplexity, args["id"].(int)), true
|
||||
|
||||
case "Query.mediaList":
|
||||
if e.complexity.Query.MediaList == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Query_mediaList_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.MediaList(childComplexity, args["ids"].([]int)), true
|
||||
|
||||
case "Query.myAlbums":
|
||||
if e.complexity.Query.MyAlbums == nil {
|
||||
break
|
||||
@@ -1310,6 +1324,9 @@ type Query {
|
||||
"Get media by id, user must own the media or be admin"
|
||||
media(id: Int!): Media!
|
||||
|
||||
"Get a list of media by their ids, user must own the media or be admin"
|
||||
mediaList(ids: [Int!]!): [Media!]!
|
||||
|
||||
"Get media owned by the logged in user, returned in GeoJson format"
|
||||
myMediaGeoJson: Any!
|
||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||
@@ -1584,6 +1601,7 @@ func (ec *executionContext) field_Album_media_args(ctx context.Context, rawArgs
|
||||
args["filter"] = arg0
|
||||
var arg1 *bool
|
||||
if tmp, ok := rawArgs["onlyFavorites"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("onlyFavorites"))
|
||||
arg1, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2010,6 +2028,21 @@ func (ec *executionContext) field_Query_album_args(ctx context.Context, rawArgs
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_mediaList_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 []int
|
||||
if tmp, ok := rawArgs["ids"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ids"))
|
||||
arg0, err = ec.unmarshalNInt2ᚕintᚄ(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["ids"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Query_media_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
@@ -2057,6 +2090,7 @@ func (ec *executionContext) field_Query_myAlbums_args(ctx context.Context, rawAr
|
||||
args["showEmpty"] = arg2
|
||||
var arg3 *bool
|
||||
if tmp, ok := rawArgs["onlyWithFavorites"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("onlyWithFavorites"))
|
||||
arg3, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -4965,6 +4999,48 @@ func (ec *executionContext) _Query_media(ctx context.Context, field graphql.Coll
|
||||
return ec.marshalNMedia2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_mediaList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Query",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Query_mediaList_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
fc.Args = args
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().MediaList(rctx, args["ids"].([]int))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.([]*models.Media)
|
||||
fc.Result = res
|
||||
return ec.marshalNMedia2ᚕᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaᚄ(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Query_myMediaGeoJson(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -8204,6 +8280,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "mediaList":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._Query_mediaList(ctx, field)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "myMediaGeoJson":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
@@ -8975,6 +9065,36 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
|
||||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNInt2ᚕintᚄ(ctx context.Context, v interface{}) ([]int, error) {
|
||||
var vSlice []interface{}
|
||||
if v != nil {
|
||||
if tmp1, ok := v.([]interface{}); ok {
|
||||
vSlice = tmp1
|
||||
} else {
|
||||
vSlice = []interface{}{v}
|
||||
}
|
||||
}
|
||||
var err error
|
||||
res := make([]int, len(vSlice))
|
||||
for i := range vSlice {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
|
||||
res[i], err = ec.unmarshalNInt2int(ctx, vSlice[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNInt2ᚕintᚄ(ctx context.Context, sel ast.SelectionSet, v []int) graphql.Marshaler {
|
||||
ret := make(graphql.Array, len(v))
|
||||
for i := range v {
|
||||
ret[i] = ec.marshalNInt2int(ctx, sel, v[i])
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMedia2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx context.Context, sel ast.SelectionSet, v models.Media) graphql.Marshaler {
|
||||
return ec._Media(ctx, sel, &v)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,44 @@ func (r *queryResolver) Media(ctx context.Context, id int) (*models.Media, error
|
||||
return media, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) MediaList(ctx context.Context, ids []int) ([]*models.Media, error) {
|
||||
user := auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return nil, errors.New("no ids provided")
|
||||
}
|
||||
|
||||
mediaIDQuestions := strings.Repeat("?,", len(ids))[:len(ids)*2-1]
|
||||
|
||||
queryArgs := make([]interface{}, 0)
|
||||
for _, id := range ids {
|
||||
queryArgs = append(queryArgs, id)
|
||||
}
|
||||
queryArgs = append(queryArgs, user.UserID)
|
||||
|
||||
rows, err := r.Database.Query(`
|
||||
SELECT media.* FROM media
|
||||
JOIN album ON media.album_id = album.album_id
|
||||
WHERE media.media_id IN (`+mediaIDQuestions+`) AND album.owner_id = ?
|
||||
AND media.media_id IN (
|
||||
SELECT media_id FROM media_url WHERE media_url.media_id = media.media_id
|
||||
)
|
||||
`, queryArgs...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get media list by media_id and user_id from database")
|
||||
}
|
||||
|
||||
media, err := models.NewMediaFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert database rows to media")
|
||||
}
|
||||
|
||||
return media, nil
|
||||
}
|
||||
|
||||
type mediaResolver struct {
|
||||
*Resolver
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ type Query {
|
||||
"Get media by id, user must own the media or be admin"
|
||||
media(id: Int!): Media!
|
||||
|
||||
"Get a list of media by their ids, user must own the media or be admin"
|
||||
mediaList(ids: [Int!]!): [Media!]!
|
||||
|
||||
"Get media owned by the logged in user, returned in GeoJson format"
|
||||
myMediaGeoJson: Any!
|
||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useLazyQuery } from 'react-apollo'
|
||||
import PresentView from '../../components/photoGallery/presentView/PresentView'
|
||||
|
||||
const QUERY_MEDIA = gql`
|
||||
query placePageQueryMedia($mediaID: Int!) {
|
||||
media(id: $mediaID) {
|
||||
query placePageQueryMedia($mediaIDs: [Int!]!) {
|
||||
mediaList(ids: $mediaIDs) {
|
||||
id
|
||||
title
|
||||
thumbnail {
|
||||
@@ -53,74 +53,51 @@ const getMediaFromMarker = (map, presentMarker) =>
|
||||
})
|
||||
|
||||
const MapPresentMarker = ({ map, presentMarker, setPresentMarker }) => {
|
||||
const [media, setMedia] = useState(null)
|
||||
const [mediaMarkers, setMediaMarkers] = useState(null)
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
|
||||
const previousLoadedMedia = useRef(null)
|
||||
const [loadMedia, { data: loadedMedia }] = useLazyQuery(QUERY_MEDIA, {
|
||||
onCompleted(data) {
|
||||
previousLoadedMedia.current = data
|
||||
},
|
||||
})
|
||||
const [loadMedia, { data: loadedMedia }] = useLazyQuery(QUERY_MEDIA)
|
||||
|
||||
useEffect(() => {
|
||||
if (presentMarker == null || map == null) {
|
||||
setMedia(null)
|
||||
setMediaMarkers(null)
|
||||
return
|
||||
}
|
||||
|
||||
getMediaFromMarker(map, presentMarker).then(setMedia)
|
||||
getMediaFromMarker(map, presentMarker).then(setMediaMarkers)
|
||||
}, [presentMarker])
|
||||
|
||||
useEffect(() => {
|
||||
if (!media) return
|
||||
if (!mediaMarkers) return
|
||||
|
||||
setCurrentIndex(0)
|
||||
loadMedia({
|
||||
variables: {
|
||||
mediaID: media[0].media_id,
|
||||
mediaIDs: mediaMarkers.map(x => x.media_id),
|
||||
},
|
||||
})
|
||||
}, [media])
|
||||
|
||||
useEffect(() => {
|
||||
if (!media) return
|
||||
|
||||
console.log('Current index change', currentIndex, media)
|
||||
|
||||
loadMedia({
|
||||
variables: {
|
||||
mediaID: media[currentIndex].media_id,
|
||||
},
|
||||
})
|
||||
}, [currentIndex])
|
||||
}, [mediaMarkers])
|
||||
|
||||
if (presentMarker == null || map == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (loadedMedia == null && previousLoadedMedia.current == null) {
|
||||
if (loadedMedia == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const displayMedia = loadedMedia
|
||||
? loadedMedia.media
|
||||
: previousLoadedMedia.current.media
|
||||
|
||||
console.log('diaplay media', displayMedia)
|
||||
|
||||
return (
|
||||
<PresentView
|
||||
media={displayMedia}
|
||||
media={loadedMedia.mediaList[currentIndex]}
|
||||
nextImage={() => {
|
||||
setCurrentIndex(i => Math.min(media.length - 1, i + 1))
|
||||
setCurrentIndex(i => Math.min(mediaMarkers.length - 1, i + 1))
|
||||
}}
|
||||
previousImage={() => {
|
||||
setCurrentIndex(i => Math.max(0, i - 1))
|
||||
}}
|
||||
setPresenting={presenting => {
|
||||
if (!presenting) {
|
||||
previousLoadedMedia.current = null
|
||||
setCurrentIndex(0)
|
||||
setPresentMarker(null)
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -61,12 +61,10 @@ const MapPage = () => {
|
||||
})
|
||||
|
||||
map.current.on('load', () => {
|
||||
console.log(mapboxData.myMediaGeoJson)
|
||||
map.current.addSource('media', {
|
||||
type: 'geojson',
|
||||
data: mapboxData.myMediaGeoJson,
|
||||
cluster: true,
|
||||
// clusterMaxZoom: 14, // Max zoom to cluster points on
|
||||
clusterRadius: 50,
|
||||
clusterProperties: {
|
||||
thumbnail: ['coalesce', ['get', 'thumbnail'], false],
|
||||
|
||||
@@ -18,7 +18,9 @@ export const makeUpdateMarkers = ({
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const coords = features[i].geometry.coordinates
|
||||
const props = features[i].properties
|
||||
const id = props.cluster ? props.cluster_id : props.media_id
|
||||
const id = props.cluster
|
||||
? `cluster_${props.cluster_id}`
|
||||
: `media_${props.media_id}`
|
||||
|
||||
let marker = markers[id]
|
||||
if (!marker) {
|
||||
|
||||
@@ -44,32 +44,6 @@ const PhotoGallery = ({
|
||||
}) => {
|
||||
const { updateSidebar } = useContext(SidebarContext)
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownEvent = e => {
|
||||
if (!onSelectImage || activeIndex == -1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key == 'ArrowRight') {
|
||||
nextImage && nextImage()
|
||||
}
|
||||
|
||||
if (e.key == 'ArrowLeft') {
|
||||
nextImage && previousImage()
|
||||
}
|
||||
|
||||
if (e.key == 'Escape' && presenting) {
|
||||
setPresenting(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownEvent)
|
||||
|
||||
return function cleanup() {
|
||||
document.removeEventListener('keydown', keyDownEvent)
|
||||
}
|
||||
})
|
||||
|
||||
const activeImage = media && activeIndex != -1 && media[activeIndex]
|
||||
|
||||
const getPhotoElements = updateSidebar => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import styled, { createGlobalStyle } from 'styled-components'
|
||||
import PresentNavigationOverlay from './PresentNavigationOverlay'
|
||||
import PresentMedia from './PresentMedia'
|
||||
@@ -28,14 +28,43 @@ const PresentView = ({
|
||||
nextImage,
|
||||
previousImage,
|
||||
setPresenting,
|
||||
}) => (
|
||||
<StyledContainer {...className}>
|
||||
<PreventScroll />
|
||||
<PresentNavigationOverlay {...{ nextImage, previousImage, setPresenting }}>
|
||||
<PresentMedia media={media} imageLoaded={imageLoaded} />
|
||||
</PresentNavigationOverlay>
|
||||
</StyledContainer>
|
||||
)
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const keyDownEvent = e => {
|
||||
if (e.key == 'ArrowRight') {
|
||||
nextImage && nextImage()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
if (e.key == 'ArrowLeft') {
|
||||
nextImage && previousImage()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
if (e.key == 'Escape') {
|
||||
setPresenting(false)
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownEvent)
|
||||
|
||||
return function cleanup() {
|
||||
document.removeEventListener('keydown', keyDownEvent)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<StyledContainer {...className}>
|
||||
<PreventScroll />
|
||||
<PresentNavigationOverlay
|
||||
{...{ nextImage, previousImage, setPresenting }}
|
||||
>
|
||||
<PresentMedia media={media} imageLoaded={imageLoaded} />
|
||||
</PresentNavigationOverlay>
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
PresentView.propTypes = {
|
||||
media: PropTypes.object.isRequired,
|
||||
|
||||
Reference in New Issue
Block a user