Start on photo favorite
This commit is contained in:
parent
66b54a97f4
commit
b289b25e34
|
@ -0,0 +1,2 @@
|
|||
-- Add favorite attribute to photos
|
||||
ALTER TABLE photo DROP favorite
|
|
@ -0,0 +1,2 @@
|
|||
-- Add favorite attribute to photos
|
||||
ALTER TABLE photo ADD favorite BOOL DEFAULT false
|
|
@ -75,6 +75,7 @@ type ComplexityRoot struct {
|
|||
CreateUser func(childComplexity int, username string, rootPath string, password *string, admin bool) int
|
||||
DeleteShareToken func(childComplexity int, token string) int
|
||||
DeleteUser func(childComplexity int, id int) int
|
||||
FavoritePhoto func(childComplexity int, photoID int, favorite bool) int
|
||||
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
|
||||
ProtectShareToken func(childComplexity int, token string, password *string) int
|
||||
RegisterUser func(childComplexity int, username string, password string, rootPath string) int
|
||||
|
@ -100,6 +101,7 @@ type ComplexityRoot struct {
|
|||
Album func(childComplexity int) int
|
||||
Downloads func(childComplexity int) int
|
||||
Exif func(childComplexity int) int
|
||||
Favorite func(childComplexity int) int
|
||||
HighRes func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Path func(childComplexity int) int
|
||||
|
@ -208,6 +210,7 @@ type MutationResolver interface {
|
|||
SharePhoto(ctx context.Context, photoID int, expire *time.Time, password *string) (*models.ShareToken, error)
|
||||
DeleteShareToken(ctx context.Context, token string) (*models.ShareToken, error)
|
||||
ProtectShareToken(ctx context.Context, token string, password *string) (*models.ShareToken, error)
|
||||
FavoritePhoto(ctx context.Context, photoID int, favorite bool) (*models.Photo, error)
|
||||
UpdateUser(ctx context.Context, id int, username *string, rootPath *string, password *string, admin *bool) (*models.User, error)
|
||||
CreateUser(ctx context.Context, username string, rootPath string, password *string, admin bool) (*models.User, error)
|
||||
DeleteUser(ctx context.Context, id int) (*models.User, error)
|
||||
|
@ -217,6 +220,7 @@ type PhotoResolver interface {
|
|||
HighRes(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error)
|
||||
Album(ctx context.Context, obj *models.Photo) (*models.Album, error)
|
||||
Exif(ctx context.Context, obj *models.Photo) (*models.PhotoEXIF, error)
|
||||
|
||||
Shares(ctx context.Context, obj *models.Photo) ([]*models.ShareToken, error)
|
||||
Downloads(ctx context.Context, obj *models.Photo) ([]*models.PhotoDownload, error)
|
||||
}
|
||||
|
@ -407,6 +411,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Mutation.DeleteUser(childComplexity, args["id"].(int)), true
|
||||
|
||||
case "Mutation.favoritePhoto":
|
||||
if e.complexity.Mutation.FavoritePhoto == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_favoritePhoto_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.FavoritePhoto(childComplexity, args["photoId"].(int), args["favorite"].(bool)), true
|
||||
|
||||
case "Mutation.initialSetupWizard":
|
||||
if e.complexity.Mutation.InitialSetupWizard == nil {
|
||||
break
|
||||
|
@ -575,6 +591,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Photo.Exif(childComplexity), true
|
||||
|
||||
case "Photo.favorite":
|
||||
if e.complexity.Photo.Favorite == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Photo.Favorite(childComplexity), true
|
||||
|
||||
case "Photo.highRes":
|
||||
if e.complexity.Photo.HighRes == nil {
|
||||
break
|
||||
|
@ -1158,6 +1181,9 @@ type Mutation {
|
|||
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
|
||||
protectShareToken(token: String!, password: String): ShareToken
|
||||
|
||||
"Mark or unmark a photo as being a favorite"
|
||||
favoritePhoto(photoId: Int!, favorite: Boolean!): Photo
|
||||
|
||||
updateUser(
|
||||
id: Int!
|
||||
username: String
|
||||
|
@ -1290,6 +1316,7 @@ type Photo {
|
|||
"The album that holds the photo"
|
||||
album: Album!
|
||||
exif: PhotoEXIF
|
||||
favorite: Boolean!
|
||||
|
||||
shares: [ShareToken!]!
|
||||
downloads: [PhotoDownload!]!
|
||||
|
@ -1449,6 +1476,28 @@ func (ec *executionContext) field_Mutation_deleteUser_args(ctx context.Context,
|
|||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_favoritePhoto_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["photoId"]; ok {
|
||||
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["photoId"] = arg0
|
||||
var arg1 bool
|
||||
if tmp, ok := rawArgs["favorite"]; ok {
|
||||
arg1, err = ec.unmarshalNBoolean2bool(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["favorite"] = arg1
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_initialSetupWizard_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
|
@ -2672,6 +2721,44 @@ func (ec *executionContext) _Mutation_protectShareToken(ctx context.Context, fie
|
|||
return ec.marshalOShareToken2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐShareToken(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_favoritePhoto(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: "Mutation",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Mutation_favoritePhoto_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.Mutation().FavoritePhoto(rctx, args["photoId"].(int), args["favorite"].(bool))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*models.Photo)
|
||||
fc.Result = res
|
||||
return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_updateUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -3347,6 +3434,40 @@ func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.Colle
|
|||
return ec.marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoEXIF(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Photo_favorite(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Photo",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Favorite, nil
|
||||
})
|
||||
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.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Photo_shares(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
@ -6512,6 +6633,8 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||
out.Values[i] = ec._Mutation_deleteShareToken(ctx, field)
|
||||
case "protectShareToken":
|
||||
out.Values[i] = ec._Mutation_protectShareToken(ctx, field)
|
||||
case "favoritePhoto":
|
||||
out.Values[i] = ec._Mutation_favoritePhoto(ctx, field)
|
||||
case "updateUser":
|
||||
out.Values[i] = ec._Mutation_updateUser(ctx, field)
|
||||
case "createUser":
|
||||
|
@ -6664,6 +6787,11 @@ func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, ob
|
|||
res = ec._Photo_exif(ctx, field, obj)
|
||||
return res
|
||||
})
|
||||
case "favorite":
|
||||
out.Values[i] = ec._Photo_favorite(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
case "shares":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
|
|
|
@ -8,11 +8,12 @@ import (
|
|||
)
|
||||
|
||||
type Photo struct {
|
||||
PhotoID int
|
||||
Title string
|
||||
Path string
|
||||
AlbumId int
|
||||
ExifId *int
|
||||
PhotoID int
|
||||
Title string
|
||||
Path string
|
||||
AlbumId int
|
||||
ExifId *int
|
||||
Favorite bool
|
||||
}
|
||||
|
||||
func (p *Photo) ID() int {
|
||||
|
@ -40,7 +41,7 @@ type PhotoURL struct {
|
|||
func NewPhotoFromRow(row *sql.Row) (*Photo, error) {
|
||||
photo := Photo{}
|
||||
|
||||
if err := row.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId); err != nil {
|
||||
if err := row.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId, &photo.Favorite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -52,7 +53,7 @@ func NewPhotosFromRows(rows *sql.Rows) ([]*Photo, error) {
|
|||
|
||||
for rows.Next() {
|
||||
var photo Photo
|
||||
if err := rows.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId); err != nil {
|
||||
if err := rows.Scan(&photo.PhotoID, &photo.Title, &photo.Path, &photo.AlbumId, &photo.ExifId, &photo.Favorite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
photos = append(photos, &photo)
|
||||
|
|
|
@ -171,3 +171,19 @@ func (r *photoResolver) Exif(ctx context.Context, obj *models.Photo) (*models.Ph
|
|||
|
||||
return exif, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) FavoritePhoto(ctx context.Context, photoID int, favourite bool) (*models.Photo, error) {
|
||||
|
||||
user := auth.UserFromContext(ctx)
|
||||
|
||||
row := r.Database.QueryRow("SELECT photo.* FROM photo JOIN album ON photo.album_id = album.album_id WHERE photo.photo_id = ? AND album.owner_id = ?", photoID, user.UserID)
|
||||
|
||||
photo, err := models.NewPhotoFromRow(row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Update photo row
|
||||
|
||||
return photo, nil
|
||||
}
|
||||
|
|
|
@ -75,6 +75,9 @@ type Mutation {
|
|||
"Set a password for a token, if null is passed for the password argument, the password will be cleared"
|
||||
protectShareToken(token: String!, password: String): ShareToken
|
||||
|
||||
"Mark or unmark a photo as being a favorite"
|
||||
favoritePhoto(photoId: Int!, favorite: Boolean!): Photo
|
||||
|
||||
updateUser(
|
||||
id: Int!
|
||||
username: String
|
||||
|
@ -207,6 +210,7 @@ type Photo {
|
|||
"The album that holds the photo"
|
||||
album: Album!
|
||||
exif: PhotoEXIF
|
||||
favorite: Boolean!
|
||||
|
||||
shares: [ShareToken!]!
|
||||
downloads: [PhotoDownload!]!
|
||||
|
|
|
@ -23,6 +23,7 @@ const photoQuery = gql`
|
|||
highRes {
|
||||
url
|
||||
}
|
||||
favorite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
import React, { useState } from 'react'
|
||||
import gql from 'graphql-tag'
|
||||
import { useMutation } from 'react-apollo'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import LazyLoad from 'react-lazyload'
|
||||
import { Icon } from 'semantic-ui-react'
|
||||
import ProtectedImage from './ProtectedImage'
|
||||
|
||||
const markFavoriteMutation = gql`
|
||||
mutation markPhotoFavorite($photoId: Int!, $favorite: Boolean!) {
|
||||
favoritePhoto(photoId: $photoId, favorite: $favorite) {
|
||||
id
|
||||
favorite
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const PhotoContainer = styled.div`
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
|
@ -107,29 +118,44 @@ export const Photo = ({
|
|||
index,
|
||||
active,
|
||||
setPresenting,
|
||||
}) => (
|
||||
<PhotoContainer
|
||||
key={photo.id}
|
||||
style={{
|
||||
cursor: onSelectImage ? 'pointer' : null,
|
||||
minWidth: `min(${minWidth}px, 100% - 8px)`,
|
||||
}}
|
||||
onClick={() => {
|
||||
onSelectImage && onSelectImage(index)
|
||||
}}
|
||||
>
|
||||
<LazyPhoto src={photo.thumbnail && photo.thumbnail.url} />
|
||||
<PhotoOverlay active={active}>
|
||||
<HoverIcon
|
||||
name="expand"
|
||||
onClick={() => {
|
||||
setPresenting(true)
|
||||
}}
|
||||
/>
|
||||
<HoverIcon name="heart outline" />
|
||||
</PhotoOverlay>
|
||||
</PhotoContainer>
|
||||
)
|
||||
}) => {
|
||||
const [markFavorite] = useMutation(markFavoriteMutation)
|
||||
|
||||
return (
|
||||
<PhotoContainer
|
||||
key={photo.id}
|
||||
style={{
|
||||
cursor: onSelectImage ? 'pointer' : null,
|
||||
minWidth: `min(${minWidth}px, 100% - 8px)`,
|
||||
}}
|
||||
onClick={() => {
|
||||
onSelectImage && onSelectImage(index)
|
||||
}}
|
||||
>
|
||||
<LazyPhoto src={photo.thumbnail && photo.thumbnail.url} />
|
||||
<PhotoOverlay active={active}>
|
||||
<HoverIcon
|
||||
name="expand"
|
||||
onClick={() => {
|
||||
setPresenting(true)
|
||||
}}
|
||||
/>
|
||||
<HoverIcon
|
||||
name={photo.favorite ? 'heart' : 'heart outline'}
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
markFavorite({
|
||||
variables: {
|
||||
photoId: photo.id,
|
||||
favorite: !photo.favorite,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</PhotoOverlay>
|
||||
</PhotoContainer>
|
||||
)
|
||||
}
|
||||
|
||||
Photo.propTypes = {
|
||||
photo: PropTypes.object.isRequired,
|
||||
|
|
Loading…
Reference in New Issue