From c3eda0feab6672381bae48965cb00df58d3de55a Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 16 Feb 2021 12:41:34 +0100 Subject: [PATCH] Start on UI for faces --- api/graphql/models/face_detection.go | 15 ++-- api/graphql/resolvers/faces.go | 4 +- ui/src/Layout.js | 4 + ui/src/Pages/FacesPage/FacesPage.js | 104 ++++++++++++++++++++++ ui/src/Pages/FacesPage/SingleFaceGroup.js | 44 +++++++++ ui/src/components/routes/Routes.js | 6 +- 6 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 ui/src/Pages/FacesPage/FacesPage.js create mode 100644 ui/src/Pages/FacesPage/SingleFaceGroup.js diff --git a/api/graphql/models/face_detection.go b/api/graphql/models/face_detection.go index 57e3e9f8..b4661a82 100644 --- a/api/graphql/models/face_detection.go +++ b/api/graphql/models/face_detection.go @@ -87,31 +87,28 @@ func (fr *FaceRectangle) Scan(value interface{}) error { return fmt.Errorf("Invalid face rectangle format, expected 4 values, got %d", len(slices)) } - minX, err := strconv.ParseFloat(slices[0], 32) + var err error + + fr.MinX, err = strconv.ParseFloat(slices[0], 32) if err != nil { return err } - maxX, err := strconv.ParseFloat(slices[0], 32) + fr.MaxX, err = strconv.ParseFloat(slices[1], 32) if err != nil { return err } - minY, err := strconv.ParseFloat(slices[0], 32) + fr.MinY, err = strconv.ParseFloat(slices[2], 32) if err != nil { return err } - maxY, err := strconv.ParseFloat(slices[0], 32) + fr.MaxY, err = strconv.ParseFloat(slices[3], 32) if err != nil { return err } - fr.MinX = float64(minX) - fr.MinX = float64(maxX) - fr.MinX = float64(minY) - fr.MinX = float64(maxY) - return nil } diff --git a/api/graphql/resolvers/faces.go b/api/graphql/resolvers/faces.go index 794f505b..b99b3437 100644 --- a/api/graphql/resolvers/faces.go +++ b/api/graphql/resolvers/faces.go @@ -30,10 +30,10 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin faceGroupMap := make(map[int][]models.ImageFace) for _, face := range imageFaces { - group, found := faceGroupMap[face.FaceGroupID] + _, found := faceGroupMap[face.FaceGroupID] if found { - group = append(group, *face) + faceGroupMap[face.FaceGroupID] = append(faceGroupMap[face.FaceGroupID], *face) } else { faceGroupMap[face.FaceGroupID] = make([]models.ImageFace, 1) faceGroupMap[face.FaceGroupID][0] = *face diff --git a/ui/src/Layout.js b/ui/src/Layout.js index c5c6fd87..0efde575 100644 --- a/ui/src/Layout.js +++ b/ui/src/Layout.js @@ -117,6 +117,10 @@ export const SideMenu = () => { Places ) : null} + + + Faces + {isAdmin ? ( diff --git a/ui/src/Pages/FacesPage/FacesPage.js b/ui/src/Pages/FacesPage/FacesPage.js new file mode 100644 index 00000000..299d6f5c --- /dev/null +++ b/ui/src/Pages/FacesPage/FacesPage.js @@ -0,0 +1,104 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { gql, useQuery } from '@apollo/client' +import Layout from '../../Layout' +import { ProtectedImage } from '../../components/photoGallery/ProtectedMedia' +import styled from 'styled-components' +import { Link } from 'react-router-dom' +import SingleFaceGroup from './SingleFaceGroup' + +const MY_FACES_QUERY = gql` + query myFaces { + myFaceGroups { + id + label + imageFaces { + id + rectangle { + minX + maxX + minY + maxY + } + media { + id + thumbnail { + url + width + height + } + } + } + } + } +` + +const CircleImageWrapper = styled.div` + border-radius: 50%; + width: 150px; + height: 150px; + object-fit: fill; + margin: 12px; + overflow: hidden; +` + +const FaceImage = styled(ProtectedImage)` + width: 100%; + height: 100%; + object-fit: cover; +` + +const FaceGroup = ({ group }) => ( + + + + + +) + +FaceGroup.propTypes = { + group: PropTypes.any, +} + +const FaceGroupsWrapper = styled.div` + display: flex; + flex-wrap: wrap; +` + +const FacesPage = ({ match }) => { + const { data, error } = useQuery(MY_FACES_QUERY) + + if (error) { + return error.message + } + + const faceGroup = match.params.face + if (faceGroup) { + return ( + + x.id == faceGroup)} + /> + + ) + } + + let faces = null + if (data) { + faces = data.myFaceGroups.map(faceGroup => ( + + )) + } + + return ( + + {faces} + + ) +} + +FacesPage.propTypes = { + match: PropTypes.object.isRequired, +} + +export default FacesPage diff --git a/ui/src/Pages/FacesPage/SingleFaceGroup.js b/ui/src/Pages/FacesPage/SingleFaceGroup.js new file mode 100644 index 00000000..0811ac18 --- /dev/null +++ b/ui/src/Pages/FacesPage/SingleFaceGroup.js @@ -0,0 +1,44 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { ProtectedImage } from '../../components/photoGallery/ProtectedMedia' +import PhotoGallery from '../../components/photoGallery/PhotoGallery' + +const ImageFace = ({ imageFace }) => { + return ( +
+ Image face: {imageFace.id} + +
+ ) +} + +ImageFace.propTypes = { + imageFace: PropTypes.object.isRequired, +} + +const SingleFaceGroup = ({ faceGroup }) => { + if (!faceGroup) { + return null + } + + // const images = faceGroup.imageFaces.map(imgFace => ( + // + // )) + + const media = faceGroup.imageFaces.map(x => x.media) + + return ( +
+ Face group: {faceGroup.id} +
+ {}} /> +
+
+ ) +} + +SingleFaceGroup.propTypes = { + faceGroup: PropTypes.object, +} + +export default SingleFaceGroup diff --git a/ui/src/components/routes/Routes.js b/ui/src/components/routes/Routes.js index e6a7cf01..fcf951d6 100644 --- a/ui/src/components/routes/Routes.js +++ b/ui/src/components/routes/Routes.js @@ -14,6 +14,7 @@ const AlbumPage = React.lazy(() => import('../../Pages/AlbumPage/AlbumPage')) const PhotosPage = React.lazy(() => import('../../Pages/PhotosPage/PhotosPage')) const PlacesPage = React.lazy(() => import('../../Pages/PlacesPage/PlacesPage')) const SharePage = React.lazy(() => import('../../Pages/SharePage/SharePage')) +const FacesPage = React.lazy(() => import('../../Pages/FacesPage/FacesPage')) const LoginPage = React.lazy(() => import('../../Pages/LoginPage/LoginPage')) const InitialSetupPage = React.lazy(() => @@ -44,9 +45,10 @@ const Routes = () => { - - + + + } />
Page not found
} />