diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index 8d8d6f76..c49c42ab 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -60,6 +60,8 @@ models:
fields:
faceGroup:
resolver: true
+ media:
+ resolver: true
FaceRectangle:
model: github.com/photoview/photoview/api/graphql/models.FaceRectangle
SiteInfo:
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index 08d80d2a..8b3ed2a4 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -288,6 +288,8 @@ type FaceGroupResolver interface {
ImageFaceCount(ctx context.Context, obj *models.FaceGroup) (int, error)
}
type ImageFaceResolver interface {
+ Media(ctx context.Context, obj *models.ImageFace) (*models.Media, error)
+
FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error)
}
type MediaResolver interface {
@@ -2054,6 +2056,7 @@ type MediaEXIF {
flash: Int
"An index describing the mode for adjusting the exposure of the image"
exposureProgram: Int
+ "GPS coordinates of where the image was taken"
coordinates: Coordinates
}
@@ -3892,14 +3895,14 @@ func (ec *executionContext) _ImageFace_media(ctx context.Context, field graphql.
Object: "ImageFace",
Field: field,
Args: nil,
- IsMethod: false,
- IsResolver: false,
+ IsMethod: true,
+ IsResolver: true,
}
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.Media, nil
+ return ec.resolvers.ImageFace().Media(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@@ -3911,9 +3914,9 @@ func (ec *executionContext) _ImageFace_media(ctx context.Context, field graphql.
}
return graphql.Null
}
- res := resTmp.(models.Media)
+ res := resTmp.(*models.Media)
fc.Result = res
- return ec.marshalNMedia2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx, field.Selections, res)
+ return ec.marshalNMedia2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx, field.Selections, res)
}
func (ec *executionContext) _ImageFace_rectangle(ctx context.Context, field graphql.CollectedField, obj *models.ImageFace) (ret graphql.Marshaler) {
@@ -10692,10 +10695,19 @@ func (ec *executionContext) _ImageFace(ctx context.Context, sel ast.SelectionSet
atomic.AddUint32(&invalids, 1)
}
case "media":
- out.Values[i] = ec._ImageFace_media(ctx, field, obj)
- if out.Values[i] == graphql.Null {
- atomic.AddUint32(&invalids, 1)
- }
+ 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._ImageFace_media(ctx, field, obj)
+ if res == graphql.Null {
+ atomic.AddUint32(&invalids, 1)
+ }
+ return res
+ })
case "rectangle":
out.Values[i] = ec._ImageFace_rectangle(ctx, field, obj)
if out.Values[i] == graphql.Null {
diff --git a/api/graphql/models/face_detection.go b/api/graphql/models/face_detection.go
index dca422f6..4c91c357 100644
--- a/api/graphql/models/face_detection.go
+++ b/api/graphql/models/face_detection.go
@@ -31,6 +31,19 @@ type ImageFace struct {
Rectangle FaceRectangle `gorm:"not null"`
}
+func (f *ImageFace) FillMedia(db *gorm.DB) error {
+ if f.Media.ID != 0 {
+ // media already exists
+ return nil
+ }
+
+ if err := db.Model(&f).Association("Media").Find(&f.Media); err != nil {
+ return err
+ }
+
+ return nil
+}
+
type FaceDescriptor face.Descriptor
// GormDataType datatype used in database
diff --git a/api/graphql/resolvers/faces.go b/api/graphql/resolvers/faces.go
index 34fe1523..2be354d3 100644
--- a/api/graphql/resolvers/faces.go
+++ b/api/graphql/resolvers/faces.go
@@ -46,6 +46,14 @@ func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace)
return &faceGroup, nil
}
+func (r imageFaceResolver) Media(ctx context.Context, obj *models.ImageFace) (*models.Media, error) {
+ if err := obj.FillMedia(r.Database); err != nil {
+ return nil, err
+ }
+
+ return &obj.Media, nil
+}
+
func (r faceGroupResolver) ImageFaces(ctx context.Context, obj *models.FaceGroup, paginate *models.Pagination) ([]*models.ImageFace, error) {
user := auth.UserFromContext(ctx)
if user == nil {
diff --git a/ui/package-lock.json b/ui/package-lock.json
index dd55fed4..bb88d29f 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -49,6 +49,7 @@
"react-test-renderer": "^17.0.2",
"styled-components": "^5.3.0",
"subscriptions-transport-ws": "^0.9.19",
+ "tailwind-override": "^0.2.3",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
"typescript": "^4.3.5",
"url-join": "^4.0.1"
@@ -24041,6 +24042,11 @@
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
+ "node_modules/tailwind-override": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/tailwind-override/-/tailwind-override-0.2.3.tgz",
+ "integrity": "sha512-psWRqXL3TiI2h/YtzRq7dwKO6N7CrsEs4v99rNHgqEclfx4IioM0cHZ9O6pzerV3E6bZi6DhCbeq0z67Xs5PIQ=="
+ },
"node_modules/tailwindcss": {
"name": "@tailwindcss/postcss7-compat",
"version": "2.2.4",
@@ -46037,6 +46043,11 @@
}
}
},
+ "tailwind-override": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/tailwind-override/-/tailwind-override-0.2.3.tgz",
+ "integrity": "sha512-psWRqXL3TiI2h/YtzRq7dwKO6N7CrsEs4v99rNHgqEclfx4IioM0cHZ9O6pzerV3E6bZi6DhCbeq0z67Xs5PIQ=="
+ },
"tailwindcss": {
"version": "npm:@tailwindcss/postcss7-compat@2.2.4",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss7-compat/-/postcss7-compat-2.2.4.tgz",
diff --git a/ui/package.json b/ui/package.json
index 4df4166f..7d7a44e4 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -49,6 +49,7 @@
"react-test-renderer": "^17.0.2",
"styled-components": "^5.3.0",
"subscriptions-transport-ws": "^0.9.19",
+ "tailwind-override": "^0.2.3",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.4",
"typescript": "^4.3.5",
"url-join": "^4.0.1"
@@ -71,12 +72,12 @@
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.1.9",
+ "apollo": "2.33.4",
+ "apollo-language-server": "1.26.3",
"husky": "^6.0.0",
"i18next-parser": "^4.2.0",
"lint-staged": "^11.0.1",
- "tsc-files": "^1.1.2",
- "apollo": "2.33.4",
- "apollo-language-server": "1.26.3"
+ "tsc-files": "^1.1.2"
},
"prettier": {
"trailingComma": "es5",
diff --git a/ui/src/components/album/AlbumTitle.tsx b/ui/src/components/album/AlbumTitle.tsx
index f5968bec..189deb9d 100644
--- a/ui/src/components/album/AlbumTitle.tsx
+++ b/ui/src/components/album/AlbumTitle.tsx
@@ -10,8 +10,10 @@ import useDelay from '../../hooks/useDelay'
import { ReactComponent as GearIcon } from './icons/gear.svg'
-const BreadcrumbList = styled.ol`
- & li::after {
+export const BreadcrumbList = styled.ol<{ hideLastArrow?: boolean }>`
+ &
+ ${({ hideLastArrow }) =>
+ hideLastArrow ? 'li:not(:last-child)::after' : 'li::after'} {
content: '';
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5px' height='6px' viewBox='0 0 5 6'%3E%3Cpolyline fill='none' stroke='%23979797' points='0.74 0.167710644 3.57228936 3 0.74 5.83228936' /%3E%3C/svg%3E");
width: 5px;
diff --git a/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx
index 059e5050..18301770 100644
--- a/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx
+++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx
@@ -23,12 +23,13 @@ import MediaSidebarMap from './MediaSidebarMap'
import {
sidebarMediaQuery,
sidebarMediaQueryVariables,
- sidebarMediaQuery_media_album,
+ sidebarMediaQuery_media_album_path,
sidebarMediaQuery_media_exif,
sidebarMediaQuery_media_faces,
sidebarMediaQuery_media_thumbnail,
sidebarMediaQuery_media_videoMetadata,
} from './__generated__/sidebarMediaQuery'
+import { BreadcrumbList } from '../../album/AlbumTitle'
const SIDEBAR_MEDIA_QUERY = gql`
query sidebarMediaQuery($id: ID!) {
@@ -82,6 +83,10 @@ const SIDEBAR_MEDIA_QUERY = gql`
album {
id
title
+ path {
+ id
+ title
+ }
}
faces {
id
@@ -95,6 +100,15 @@ const SIDEBAR_MEDIA_QUERY = gql`
id
label
}
+ media {
+ id
+ title
+ thumbnail {
+ url
+ width
+ height
+ }
+ }
}
}
}
@@ -162,20 +176,26 @@ const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => {
sidebarMap =