1
Fork 0

Make people page translatable

This commit is contained in:
viktorstrate 2021-05-19 21:32:49 +02:00
parent e2f6bdb365
commit 360db25ec3
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
16 changed files with 671 additions and 161 deletions

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Navn", "action": {
"unlabeled": "Ikke navngivet" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Genkend ikke navngivede ansigter" "label_placeholder": "Navn",
"unlabeled": "Ikke navngivet",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Genkend ikke navngivede ansigter",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Billeder" "title": "Billeder"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Zuordnung", "action": {
"unlabeled": "Nicht zugeordnet" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Nicht zugeordnete Gesichter erkennen" "label_placeholder": "Zuordnung",
"unlabeled": "Nicht zugeordnet",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Nicht zugeordnete Gesichter erkennen",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Fotos" "title": "Fotos"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Label", "action": {
"unlabeled": "Unlabeled" "add_label": "Add Label",
"change_label": "Change Label",
"detach_face": "Detach Face",
"merge_face": "Merge Face",
"move_faces": "Move Faces"
}, },
"recognize_unlabeled_faces_button": "Recognize unlabeled faces" "label_placeholder": "Label",
"unlabeled": "Unlabeled",
"unlabeled_person": "Unlabeled person"
},
"modal": {
"action": {
"merge": "Merge"
},
"detach_image_faces": {
"action": {
"detach": "Detach image faces",
"select_images": "Select images to detach"
},
"description": "Detach selected images of this face group and move them to a new face groups",
"title": "Detach Image Faces"
},
"merge_face_groups": {
"description": "All images within this face group will be merged into the selected face group.",
"destination_table": {
"title": "Select the destination face"
},
"title": "Merge Face Groups"
},
"move_image_faces": {
"description": "Move selected images of this face group to another face group",
"destination_face_group_table": {
"move_action": "Move image faces",
"title": "Select destination face group"
},
"image_select_table": {
"next_action": "Next",
"title": "Select images to move"
},
"title": "Move Image Faces"
}
},
"recognize_unlabeled_faces_button": "Recognize unlabeled faces",
"table": {
"select_face_group": {
"search_faces_placeholder": "Search faces..."
},
"select_image_faces": {
"search_images_placeholder": "Search images..."
}
}
}, },
"photos_page": { "photos_page": {
"title": "Photos" "title": "Photos"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Etiqueta", "action": {
"unlabeled": "Sin etiquetar" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Reconocer caras sin etiquetar" "label_placeholder": "Etiqueta",
"unlabeled": "Sin etiquetar",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Reconocer caras sin etiquetar",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Fotos" "title": "Fotos"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Étiquette", "action": {
"unlabeled": "Sans étiquette" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Reconnaître les visages sans étiquette" "label_placeholder": "Étiquette",
"unlabeled": "Sans étiquette",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Reconnaître les visages sans étiquette",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Photos" "title": "Photos"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Etichetta", "action": {
"unlabeled": "Senza etichetta" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Identifica facce senza etichetta" "label_placeholder": "Etichetta",
"unlabeled": "Senza etichetta",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Identifica facce senza etichetta",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Foto" "title": "Foto"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Etykieta", "action": {
"unlabeled": "Nieoznakowany" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Rozpoznaj nieoznakowane twarze" "label_placeholder": "Etykieta",
"unlabeled": "Nieoznakowany",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Rozpoznaj nieoznakowane twarze",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Zdjęcia" "title": "Zdjęcia"

View File

@ -68,10 +68,58 @@
}, },
"people_page": { "people_page": {
"face_group": { "face_group": {
"label_placeholder": "Märkning", "action": {
"unlabeled": "Omärkt" "add_label": null,
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
}, },
"recognize_unlabeled_faces_button": "Känna igen omärkta ansikten" "label_placeholder": "Märkning",
"unlabeled": "Omärkt",
"unlabeled_person": null
},
"modal": {
"action": {
"merge": null
},
"detach_image_faces": {
"action": {
"detach": null,
"select_images": null
},
"description": null,
"title": null
},
"merge_face_groups": {
"description": null,
"destination_table": {
"title": null
},
"title": null
},
"move_image_faces": {
"description": null,
"destination_face_group_table": {
"move_action": null,
"title": null
},
"image_select_table": {
"next_action": null,
"title": null
},
"title": null
}
},
"recognize_unlabeled_faces_button": "Känna igen omärkta ansikten",
"table": {
"select_face_group": {
"search_faces_placeholder": null
},
"select_image_faces": {
"search_images_placeholder": null
}
}
}, },
"photos_page": { "photos_page": {
"title": "Bilder" "title": "Bilder"

View File

@ -1,10 +1,23 @@
import { gql, useMutation } from '@apollo/client' import { gql, useMutation } from '@apollo/client'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { Button, Modal } from 'semantic-ui-react' import { Button, Modal } from 'semantic-ui-react'
import { isNil } from '../../../helpers/utils'
import { MY_FACES_QUERY } from '../PeoplePage' import { MY_FACES_QUERY } from '../PeoplePage'
import {
myFaces_myFaceGroups,
myFaces_myFaceGroups_imageFaces,
} from '../__generated__/myFaces'
import SelectImageFacesTable from './SelectImageFacesTable' import SelectImageFacesTable from './SelectImageFacesTable'
import {
detachImageFaces,
detachImageFacesVariables,
} from './__generated__/detachImageFaces'
import {
singleFaceGroup_faceGroup,
singleFaceGroup_faceGroup_imageFaces,
} from './__generated__/singleFaceGroup'
const DETACH_IMAGE_FACES_MUTATION = gql` const DETACH_IMAGE_FACES_MUTATION = gql`
mutation detachImageFaces($faceIDs: [ID!]!) { mutation detachImageFaces($faceIDs: [ID!]!) {
@ -15,12 +28,28 @@ const DETACH_IMAGE_FACES_MUTATION = gql`
} }
` `
const DetachImageFacesModal = ({ open, setOpen, faceGroup }) => { type DetachImageFacesModalProps = {
const [selectedImageFaces, setSelectedImageFaces] = useState([]) open: boolean
let history = useHistory() setOpen(open: boolean): void
faceGroup: myFaces_myFaceGroups | singleFaceGroup_faceGroup
}
const [detachImageFacesMutation] = useMutation(DETACH_IMAGE_FACES_MUTATION, { const DetachImageFacesModal = ({
variables: {}, open,
setOpen,
faceGroup,
}: DetachImageFacesModalProps) => {
const { t } = useTranslation()
const [selectedImageFaces, setSelectedImageFaces] = useState<
(myFaces_myFaceGroups_imageFaces | singleFaceGroup_faceGroup_imageFaces)[]
>([])
const history = useHistory()
const [detachImageFacesMutation] = useMutation<
detachImageFaces,
detachImageFacesVariables
>(DETACH_IMAGE_FACES_MUTATION, {
refetchQueries: [ refetchQueries: [
{ {
query: MY_FACES_QUERY, query: MY_FACES_QUERY,
@ -44,6 +73,7 @@ const DetachImageFacesModal = ({ open, setOpen, faceGroup }) => {
faceIDs, faceIDs,
}, },
}).then(({ data }) => { }).then(({ data }) => {
if (isNil(data)) throw new Error('Expected data not to be null')
setOpen(false) setOpen(false)
history.push(`/people/${data.detachImageFaces.id}`) history.push(`/people/${data.detachImageFaces.id}`)
}) })
@ -57,18 +87,25 @@ const DetachImageFacesModal = ({ open, setOpen, faceGroup }) => {
onOpen={() => setOpen(true)} onOpen={() => setOpen(true)}
open={open} open={open}
> >
<Modal.Header>Detach Image Faces</Modal.Header> <Modal.Header>
{t('people_page.modal.detach_image_faces.title', 'Detach Image Faces')}
</Modal.Header>
<Modal.Content scrolling> <Modal.Content scrolling>
<Modal.Description> <Modal.Description>
<p> <p>
Detach selected images of this face group and move them to a new {t(
face group 'people_page.modal.detach_image_faces.description',
'Detach selected images of this face group and move them to a new face groups'
)}
</p> </p>
<SelectImageFacesTable <SelectImageFacesTable
imageFaces={imageFaces} imageFaces={imageFaces}
selectedImageFaces={selectedImageFaces} selectedImageFaces={selectedImageFaces}
setSelectedImageFaces={setSelectedImageFaces} setSelectedImageFaces={setSelectedImageFaces}
title="Select images to detach" title={t(
'people_page.modal.detach_image_faces.action.select_images',
'Select images to detach'
)}
/> />
</Modal.Description> </Modal.Description>
</Modal.Content> </Modal.Content>
@ -76,7 +113,10 @@ const DetachImageFacesModal = ({ open, setOpen, faceGroup }) => {
<Button onClick={() => setOpen(false)}>Cancel</Button> <Button onClick={() => setOpen(false)}>Cancel</Button>
<Button <Button
disabled={selectedImageFaces.length == 0} disabled={selectedImageFaces.length == 0}
content="Detach image faces" content={t(
'people_page.modal.detach_image_faces.action.detach',
'Detach image faces'
)}
labelPosition="right" labelPosition="right"
icon="checkmark" icon="checkmark"
onClick={() => detachImageFaces()} onClick={() => detachImageFaces()}
@ -87,10 +127,4 @@ const DetachImageFacesModal = ({ open, setOpen, faceGroup }) => {
) )
} }
DetachImageFacesModal.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
faceGroup: PropTypes.object,
}
export default DetachImageFacesModal export default DetachImageFacesModal

View File

@ -1,5 +1,6 @@
import { useMutation } from '@apollo/client' import { useMutation } from '@apollo/client'
import React, { useState, useEffect, createRef } from 'react' import React, { useState, useEffect, createRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Dropdown, Input } from 'semantic-ui-react' import { Dropdown, Input } from 'semantic-ui-react'
import styled from 'styled-components' import styled from 'styled-components'
import { isNil } from '../../../helpers/utils' import { isNil } from '../../../helpers/utils'
@ -38,6 +39,8 @@ type FaceGroupTitleProps = {
} }
const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => { const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
const { t } = useTranslation()
const [editLabel, setEditLabel] = useState(false) const [editLabel, setEditLabel] = useState(false)
const [inputValue, setInputValue] = useState(faceGroup?.label ?? '') const [inputValue, setInputValue] = useState(faceGroup?.label ?? '')
const inputRef = createRef<Input>() const inputRef = createRef<Input>()
@ -91,7 +94,8 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
title = ( title = (
<TitleWrapper> <TitleWrapper>
<TitleLabel labeled={!!faceGroup?.label}> <TitleLabel labeled={!!faceGroup?.label}>
{faceGroup?.label ?? 'Unlabeled person'} {faceGroup?.label ??
t('people_page.face_group.unlabeled_person', 'Unlabeled person')}
</TitleLabel> </TitleLabel>
<TitleDropdown <TitleDropdown
icon={{ icon={{
@ -102,22 +106,32 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
<Dropdown.Menu> <Dropdown.Menu>
<Dropdown.Item <Dropdown.Item
icon="pencil" icon="pencil"
text={faceGroup?.label ? 'Change Label' : 'Add Label'} text={
faceGroup?.label
? t(
'people_page.face_group.action.change_label',
'Change Label'
)
: t('people_page.face_group.action.add_label', 'Add Label')
}
onClick={() => setEditLabel(true)} onClick={() => setEditLabel(true)}
/> />
<Dropdown.Item <Dropdown.Item
icon="object group" icon="object group"
text="Merge Face" text={t('people_page.face_group.action.merge_face', 'Merge Face')}
onClick={() => setMergeModalOpen(true)} onClick={() => setMergeModalOpen(true)}
/> />
<Dropdown.Item <Dropdown.Item
icon="object ungroup" icon="object ungroup"
text="Detach Faces" text={t(
'people_page.face_group.action.detach_face',
'Detach Face'
)}
onClick={() => setDetachModalOpen(true)} onClick={() => setDetachModalOpen(true)}
/> />
<Dropdown.Item <Dropdown.Item
icon="clone" icon="clone"
text="Move Faces" text={t('people_page.face_group.action.move_faces', 'Move Faces')}
onClick={() => setMoveModalOpen(true)} onClick={() => setMoveModalOpen(true)}
/> />
</Dropdown.Menu> </Dropdown.Menu>
@ -130,7 +144,7 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
<Input <Input
loading={setLabelLoading} loading={setLabelLoading}
ref={inputRef} ref={inputRef}
placeholder="Label" placeholder={t('people_page.face_group.label_placeholder', 'Label')}
icon="arrow right" icon="arrow right"
value={inputValue} value={inputValue}
onKeyUp={onKeyUp} onKeyUp={onKeyUp}
@ -143,9 +157,10 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
) )
} }
return ( let modals = null
if (faceGroup) {
modals = (
<> <>
{title}
<MergeFaceGroupsModal <MergeFaceGroupsModal
open={mergeModalOpen} open={mergeModalOpen}
setOpen={setMergeModalOpen} setOpen={setMergeModalOpen}
@ -163,6 +178,14 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
/> />
</> </>
) )
}
return (
<>
{title}
{modals}
</>
)
} }
export default FaceGroupTitle export default FaceGroupTitle

View File

@ -1,94 +0,0 @@
import { gql, useMutation, useQuery } from '@apollo/client'
import PropTypes from 'prop-types'
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom'
import { Button, Modal } from 'semantic-ui-react'
import { MY_FACES_QUERY } from '../PeoplePage'
import SelectFaceGroupTable from './SelectFaceGroupTable'
const COMBINE_FACES_MUTATION = gql`
mutation combineFaces($destID: ID!, $srcID: ID!) {
combineFaceGroups(
destinationFaceGroupID: $destID
sourceFaceGroupID: $srcID
) {
id
}
}
`
const MergeFaceGroupsModal = ({ open, setOpen, sourceFaceGroup }) => {
const [selectedFaceGroup, setSelectedFaceGroup] = useState(null)
let history = useHistory()
const { data } = useQuery(MY_FACES_QUERY)
const [combineFacesMutation] = useMutation(COMBINE_FACES_MUTATION, {
variables: {
srcID: sourceFaceGroup?.id,
},
refetchQueries: [
{
query: MY_FACES_QUERY,
},
],
})
if (open == false) return null
const filteredFaceGroups =
data?.myFaceGroups.filter(x => x.id != sourceFaceGroup?.id) ?? []
const mergeFaceGroups = () => {
combineFacesMutation({
variables: {
destID: selectedFaceGroup.id,
},
}).then(() => {
setOpen(false)
history.push(`/people/${selectedFaceGroup.id}`)
})
}
return (
<Modal
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
>
<Modal.Header>Merge Face Groups</Modal.Header>
<Modal.Content scrolling>
<Modal.Description>
<p>
All images within this face group will be merged into the selected
face group.
</p>
<SelectFaceGroupTable
title="Select the destination face"
faceGroups={filteredFaceGroups}
selectedFaceGroup={selectedFaceGroup}
setSelectedFaceGroup={setSelectedFaceGroup}
/>
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button
disabled={selectedFaceGroup == null}
content="Merge"
labelPosition="right"
icon="checkmark"
onClick={() => mergeFaceGroups()}
positive
/>
</Modal.Actions>
</Modal>
)
}
MergeFaceGroupsModal.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
sourceFaceGroup: PropTypes.object,
}
export default MergeFaceGroupsModal

View File

@ -0,0 +1,124 @@
import { gql, useMutation, useQuery } from '@apollo/client'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { Button, Modal } from 'semantic-ui-react'
import { isNil } from '../../../helpers/utils'
import { MY_FACES_QUERY } from '../PeoplePage'
import {
myFaces,
myFacesVariables,
myFaces_myFaceGroups,
} from '../__generated__/myFaces'
import SelectFaceGroupTable from './SelectFaceGroupTable'
import {
combineFaces,
combineFacesVariables,
} from './__generated__/combineFaces'
import { singleFaceGroup_faceGroup } from './__generated__/singleFaceGroup'
const COMBINE_FACES_MUTATION = gql`
mutation combineFaces($destID: ID!, $srcID: ID!) {
combineFaceGroups(
destinationFaceGroupID: $destID
sourceFaceGroupID: $srcID
) {
id
}
}
`
type MergeFaceGroupsModalProps = {
open: boolean
setOpen(open: boolean): void
sourceFaceGroup: myFaces_myFaceGroups | singleFaceGroup_faceGroup
}
const MergeFaceGroupsModal = ({
open,
setOpen,
sourceFaceGroup,
}: MergeFaceGroupsModalProps) => {
const { t } = useTranslation()
const [selectedFaceGroup, setSelectedFaceGroup] =
useState<myFaces_myFaceGroups | singleFaceGroup_faceGroup | null>(null)
const history = useHistory()
const { data } = useQuery<myFaces, myFacesVariables>(MY_FACES_QUERY)
const [combineFacesMutation] = useMutation<
combineFaces,
combineFacesVariables
>(COMBINE_FACES_MUTATION, {
refetchQueries: [
{
query: MY_FACES_QUERY,
},
],
})
if (open == false) return null
const filteredFaceGroups =
data?.myFaceGroups.filter(x => x.id != sourceFaceGroup?.id) ?? []
const mergeFaceGroups = () => {
if (isNil(selectedFaceGroup)) throw new Error('No selected face group')
combineFacesMutation({
variables: {
srcID: sourceFaceGroup.id,
destID: selectedFaceGroup.id,
},
}).then(() => {
setOpen(false)
history.push(`/people/${selectedFaceGroup.id}`)
})
}
return (
<Modal
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
>
<Modal.Header>
{t('people_page.modal.merge_face_groups.title', 'Merge Face Groups')}
</Modal.Header>
<Modal.Content scrolling>
<Modal.Description>
<p>
{t(
'people_page.modal.merge_face_groups.description',
'All images within this face group will be merged into the selected face group.'
)}
</p>
<SelectFaceGroupTable
title={t(
'people_page.modal.merge_face_groups.destination_table.title',
'Select the destination face'
)}
faceGroups={filteredFaceGroups}
selectedFaceGroup={selectedFaceGroup}
setSelectedFaceGroup={setSelectedFaceGroup}
/>
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => setOpen(false)}>
{t('general.action.cancel', 'Cancel')}
</Button>
<Button
disabled={selectedFaceGroup == null}
content={t('people_page.modal.action.merge', 'Merge')}
labelPosition="right"
icon="checkmark"
onClick={() => mergeFaceGroups()}
positive
/>
</Modal.Actions>
</Modal>
)
}
export default MergeFaceGroupsModal

View File

@ -20,6 +20,7 @@ import {
moveImageFaces, moveImageFaces,
moveImageFacesVariables, moveImageFacesVariables,
} from './__generated__/moveImageFaces' } from './__generated__/moveImageFaces'
import { useTranslation } from 'react-i18next'
const MOVE_IMAGE_FACES_MUTATION = gql` const MOVE_IMAGE_FACES_MUTATION = gql`
mutation moveImageFaces($faceIDs: [ID!]!, $destFaceGroupID: ID!) { mutation moveImageFaces($faceIDs: [ID!]!, $destFaceGroupID: ID!) {
@ -38,7 +39,7 @@ const MOVE_IMAGE_FACES_MUTATION = gql`
type MoveImageFacesModalProps = { type MoveImageFacesModalProps = {
open: boolean open: boolean
setOpen: React.Dispatch<React.SetStateAction<boolean>> setOpen: React.Dispatch<React.SetStateAction<boolean>>
faceGroup?: singleFaceGroup_faceGroup faceGroup: singleFaceGroup_faceGroup
} }
const MoveImageFacesModal = ({ const MoveImageFacesModal = ({
@ -46,6 +47,8 @@ const MoveImageFacesModal = ({
setOpen, setOpen,
faceGroup, faceGroup,
}: MoveImageFacesModalProps) => { }: MoveImageFacesModalProps) => {
const { t } = useTranslation()
const [selectedImageFaces, setSelectedImageFaces] = useState< const [selectedImageFaces, setSelectedImageFaces] = useState<
(singleFaceGroup_faceGroup_imageFaces | myFaces_myFaceGroups_imageFaces)[] (singleFaceGroup_faceGroup_imageFaces | myFaces_myFaceGroups_imageFaces)[]
>([]) >([])
@ -102,7 +105,7 @@ const MoveImageFacesModal = ({
}) })
} }
const imageFaces = faceGroup?.imageFaces ?? [] const imageFaces = faceGroup.imageFaces
let table = null let table = null
if (!imagesSelected) { if (!imagesSelected) {
@ -111,7 +114,10 @@ const MoveImageFacesModal = ({
imageFaces={imageFaces} imageFaces={imageFaces}
selectedImageFaces={selectedImageFaces} selectedImageFaces={selectedImageFaces}
setSelectedImageFaces={setSelectedImageFaces} setSelectedImageFaces={setSelectedImageFaces}
title="Select images to move" title={t(
'people_page.modal.move_image_faces.image_select_table.title',
'Select images to move'
)}
/> />
) )
} else { } else {
@ -121,14 +127,17 @@ const MoveImageFacesModal = ({
) )
table = ( table = (
<SelectFaceGroupTable <SelectFaceGroupTable
title="Select destination face group" title={t(
'people_page.modal.move_image_faces.destination_face_group_table.title',
'Select destination face group'
)}
faceGroups={filteredFaceGroups} faceGroups={filteredFaceGroups}
selectedFaceGroup={selectedFaceGroup} selectedFaceGroup={selectedFaceGroup}
setSelectedFaceGroup={setSelectedFaceGroup} setSelectedFaceGroup={setSelectedFaceGroup}
/> />
) )
} else { } else {
table = <div>Loading...</div> table = <div>{t('general.loading.default', 'Loading...')}</div>
} }
} }
@ -137,7 +146,10 @@ const MoveImageFacesModal = ({
positiveButton = ( positiveButton = (
<Button <Button
disabled={selectedImageFaces.length == 0} disabled={selectedImageFaces.length == 0}
content="Next" content={t(
'people_page.modal.move_image_faces.image_select_table.next_action',
'Next'
)}
labelPosition="right" labelPosition="right"
icon="arrow right" icon="arrow right"
onClick={() => setImagesSelected(true)} onClick={() => setImagesSelected(true)}
@ -148,7 +160,10 @@ const MoveImageFacesModal = ({
positiveButton = ( positiveButton = (
<Button <Button
disabled={!selectedFaceGroup} disabled={!selectedFaceGroup}
content="Move image faces" content={t(
'people_page.modal.move_image_faces.destination_face_group_table.move_action',
'Move image faces'
)}
labelPosition="right" labelPosition="right"
icon="checkmark" icon="checkmark"
onClick={() => moveImageFaces()} onClick={() => moveImageFaces()}
@ -163,15 +178,24 @@ const MoveImageFacesModal = ({
onOpen={() => setOpen(true)} onOpen={() => setOpen(true)}
open={open} open={open}
> >
<Modal.Header>Move Image Faces</Modal.Header> <Modal.Header>
{t('people_page.modal.move_image_faces.title', 'Move Image Faces')}
</Modal.Header>
<Modal.Content scrolling> <Modal.Content scrolling>
<Modal.Description> <Modal.Description>
<p>Move selected images of this face group to another face group</p> <p>
{t(
'people_page.modal.move_image_faces.description',
'Move selected images of this face group to another face group'
)}
</p>
{table} {table}
</Modal.Description> </Modal.Description>
</Modal.Content> </Modal.Content>
<Modal.Actions> <Modal.Actions>
<Button onClick={() => setOpen(false)}>Cancel</Button> <Button onClick={() => setOpen(false)}>
{t('general.action.cancel', 'Cancel')}
</Button>
{positiveButton} {positiveButton}
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Input, Pagination, Table } from 'semantic-ui-react' import { Input, Pagination, Table } from 'semantic-ui-react'
import styled from 'styled-components' import styled from 'styled-components'
import FaceCircleImage from '../FaceCircleImage' import FaceCircleImage from '../FaceCircleImage'
@ -66,6 +67,8 @@ const SelectFaceGroupTable = ({
setSelectedFaceGroup, setSelectedFaceGroup,
title, title,
}: SelectFaceGroupTableProps) => { }: SelectFaceGroupTableProps) => {
const { t } = useTranslation()
const PAGE_SIZE = 6 const PAGE_SIZE = 6
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
@ -106,7 +109,10 @@ const SelectFaceGroupTable = ({
value={searchValue} value={searchValue}
onChange={e => setSearchValue(e.target.value)} onChange={e => setSearchValue(e.target.value)}
icon="search" icon="search"
placeholder="Search faces..." placeholder={t(
'people_page.table.select_face_group.search_faces_placeholder',
'Search faces...'
)}
fluid fluid
/> />
</Table.HeaderCell> </Table.HeaderCell>

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Checkbox, Input, Pagination, Table } from 'semantic-ui-react' import { Checkbox, Input, Pagination, Table } from 'semantic-ui-react'
import styled from 'styled-components' import styled from 'styled-components'
import { ProtectedImage } from '../../../components/photoGallery/ProtectedMedia' import { ProtectedImage } from '../../../components/photoGallery/ProtectedMedia'
@ -67,6 +68,8 @@ const SelectImageFacesTable = ({
setSelectedImageFaces, setSelectedImageFaces,
title, title,
}: SelectImageFacesTable) => { }: SelectImageFacesTable) => {
const { t } = useTranslation()
const PAGE_SIZE = 6 const PAGE_SIZE = 6
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
@ -115,7 +118,10 @@ const SelectImageFacesTable = ({
value={searchValue} value={searchValue}
onChange={e => setSearchValue(e.target.value)} onChange={e => setSearchValue(e.target.value)}
icon="search" icon="search"
placeholder="Search images..." placeholder={t(
'people_page.table.select_image_faces.search_images_placeholder',
'Search images...'
)}
fluid fluid
/> />
</Table.HeaderCell> </Table.HeaderCell>

View File

@ -1,5 +1,6 @@
import { gql, useQuery } from '@apollo/client' import { gql, useQuery } from '@apollo/client'
import React, { useEffect, useReducer } from 'react' import React, { useEffect, useReducer } from 'react'
import { useTranslation } from 'react-i18next'
import PaginateLoader from '../../../components/PaginateLoader' import PaginateLoader from '../../../components/PaginateLoader'
import PhotoGallery from '../../../components/photoGallery/PhotoGallery' import PhotoGallery from '../../../components/photoGallery/PhotoGallery'
import { photoGalleryReducer } from '../../../components/photoGallery/photoGalleryReducer' import { photoGalleryReducer } from '../../../components/photoGallery/photoGalleryReducer'
@ -47,6 +48,8 @@ type SingleFaceGroupProps = {
} }
const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => { const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => {
const { t } = useTranslation()
const { data, error, loading, fetchMore } = useQuery< const { data, error, loading, fetchMore } = useQuery<
singleFaceGroup, singleFaceGroup,
singleFaceGroupVariables singleFaceGroupVariables
@ -94,7 +97,7 @@ const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => {
/> />
<PaginateLoader <PaginateLoader
active={!finishedLoadingMore && !loading} active={!finishedLoadingMore && !loading}
text="Loading more photos" text={t('general.loading.paginate.media', 'Loading more media')}
/> />
</div> </div>
</div> </div>