mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 19:29:15 +00:00
Start on UI for faces
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -117,6 +117,10 @@ export const SideMenu = () => {
|
||||
<SideButtonLabel>Places</SideButtonLabel>
|
||||
</SideButton>
|
||||
) : null}
|
||||
<SideButton to="/faces" exact>
|
||||
<Icon name="user outline" />
|
||||
<SideButtonLabel>Faces</SideButtonLabel>
|
||||
</SideButton>
|
||||
{isAdmin ? (
|
||||
<SideButton to="/settings" exact>
|
||||
<Icon name="settings" />
|
||||
|
||||
104
ui/src/Pages/FacesPage/FacesPage.js
Normal file
104
ui/src/Pages/FacesPage/FacesPage.js
Normal file
@@ -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 }) => (
|
||||
<Link to={`/faces/${group.id}`}>
|
||||
<CircleImageWrapper>
|
||||
<FaceImage src={group.imageFaces[0].media.thumbnail.url} />
|
||||
</CircleImageWrapper>
|
||||
</Link>
|
||||
)
|
||||
|
||||
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 (
|
||||
<Layout>
|
||||
<SingleFaceGroup
|
||||
faceGroup={data?.myFaceGroups?.find(x => x.id == faceGroup)}
|
||||
/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
let faces = null
|
||||
if (data) {
|
||||
faces = data.myFaceGroups.map(faceGroup => (
|
||||
<FaceGroup key={faceGroup.id} group={faceGroup} />
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<FaceGroupsWrapper>{faces}</FaceGroupsWrapper>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
FacesPage.propTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default FacesPage
|
||||
44
ui/src/Pages/FacesPage/SingleFaceGroup.js
Normal file
44
ui/src/Pages/FacesPage/SingleFaceGroup.js
Normal file
@@ -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 (
|
||||
<div>
|
||||
Image face: {imageFace.id}
|
||||
<ProtectedImage src={imageFace.media.thumbnail.url} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ImageFace.propTypes = {
|
||||
imageFace: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const SingleFaceGroup = ({ faceGroup }) => {
|
||||
if (!faceGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
// const images = faceGroup.imageFaces.map(imgFace => (
|
||||
// <ImageFace key={imgFace.id} imageFace={imgFace} />
|
||||
// ))
|
||||
|
||||
const media = faceGroup.imageFaces.map(x => x.media)
|
||||
|
||||
return (
|
||||
<div>
|
||||
Face group: {faceGroup.id}
|
||||
<div>
|
||||
<PhotoGallery media={media} loading={false} setPresenting={() => {}} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
SingleFaceGroup.propTypes = {
|
||||
faceGroup: PropTypes.object,
|
||||
}
|
||||
|
||||
export default SingleFaceGroup
|
||||
@@ -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 = () => {
|
||||
<Route path="/initialSetup" component={InitialSetupPage} />
|
||||
<Route path="/share" component={SharePage} />
|
||||
<AuthorizedRoute exact path="/albums" component={AlbumsPage} />
|
||||
<AuthorizedRoute path="/album/:id/:subPage?" component={AlbumPage} />
|
||||
<AuthorizedRoute path="/photos/:subPage?" component={PhotosPage} />
|
||||
<AuthorizedRoute path="/album/:id" component={AlbumPage} />
|
||||
<AuthorizedRoute path="/photos" component={PhotosPage} />
|
||||
<AuthorizedRoute path="/places" component={PlacesPage} />
|
||||
<AuthorizedRoute path="/faces/:face?" component={FacesPage} />
|
||||
<AuthorizedRoute admin path="/settings" component={SettingsPage} />
|
||||
<Route path="/" exact render={() => <Redirect to="/photos" />} />
|
||||
<Route render={() => <div>Page not found</div>} />
|
||||
|
||||
Reference in New Issue
Block a user