Add functionality to sidebar people menu
This commit is contained in:
parent
e27f653c2e
commit
df57f55ac4
|
@ -144,7 +144,11 @@ describe('FaceDetails component', () => {
|
|||
|
||||
render(
|
||||
<MockedProvider mocks={[]} addTypename={false}>
|
||||
<FaceDetails group={emptyFaceGroup} />
|
||||
<FaceDetails
|
||||
editLabel={false}
|
||||
setEditLabel={jest.fn()}
|
||||
group={emptyFaceGroup}
|
||||
/>
|
||||
</MockedProvider>
|
||||
)
|
||||
|
||||
|
@ -159,7 +163,11 @@ describe('FaceDetails component', () => {
|
|||
|
||||
render(
|
||||
<MockedProvider mocks={[]} addTypename={false}>
|
||||
<FaceDetails group={labeledFaceGroup} />
|
||||
<FaceDetails
|
||||
editLabel={false}
|
||||
setEditLabel={jest.fn()}
|
||||
group={labeledFaceGroup}
|
||||
/>
|
||||
</MockedProvider>
|
||||
)
|
||||
|
||||
|
@ -190,7 +198,11 @@ describe('FaceDetails component', () => {
|
|||
]
|
||||
render(
|
||||
<MockedProvider mocks={graphqlMocks} addTypename={false}>
|
||||
<FaceDetails group={faceGroup} />
|
||||
<FaceDetails
|
||||
editLabel={false}
|
||||
setEditLabel={jest.fn()}
|
||||
group={faceGroup}
|
||||
/>
|
||||
</MockedProvider>
|
||||
)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
myFaces_myFaceGroups,
|
||||
} from './__generated__/myFaces'
|
||||
import { recognizeUnlabeledFaces } from './__generated__/recognizeUnlabeledFaces'
|
||||
import { tailwindClassNames } from '../../helpers/utils'
|
||||
|
||||
export const MY_FACES_QUERY = gql`
|
||||
query myFaces($limit: Int, $offset: Int) {
|
||||
|
@ -65,15 +66,8 @@ const RECOGNIZE_UNLABELED_FACES_MUTATION = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const FaceDetailsWrapper = styled.div<{ labeled: boolean }>`
|
||||
const FaceDetailsWrapper = styled.span<{ labeled: boolean }>`
|
||||
color: ${({ labeled }) => (labeled ? 'black' : '#aaa')};
|
||||
width: 150px;
|
||||
margin: 12px auto 24px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
|
@ -82,12 +76,26 @@ const FaceDetailsWrapper = styled.div<{ labeled: boolean }>`
|
|||
`
|
||||
|
||||
type FaceDetailsProps = {
|
||||
group: myFaces_myFaceGroups
|
||||
group: {
|
||||
__typename: 'FaceGroup'
|
||||
id: string
|
||||
label: string | null
|
||||
imageFaceCount: number
|
||||
}
|
||||
className?: string
|
||||
textFieldClassName?: string
|
||||
editLabel: boolean
|
||||
setEditLabel: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
||||
export const FaceDetails = ({
|
||||
group,
|
||||
className,
|
||||
textFieldClassName,
|
||||
editLabel,
|
||||
setEditLabel,
|
||||
}: FaceDetailsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [editLabel, setEditLabel] = useState(false)
|
||||
const [inputValue, setInputValue] = useState(group.label ?? '')
|
||||
const inputRef = createRef<HTMLInputElement>()
|
||||
|
||||
|
@ -126,11 +134,15 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
|||
if (!editLabel) {
|
||||
label = (
|
||||
<FaceDetailsWrapper
|
||||
className={tailwindClassNames(
|
||||
className,
|
||||
'whitespace-nowrap inline-block overflow-hidden overflow-clip'
|
||||
)}
|
||||
labeled={!!group.label}
|
||||
onClick={() => setEditLabel(true)}
|
||||
>
|
||||
<FaceImagesCount>{group.imageFaceCount}</FaceImagesCount>
|
||||
<button>
|
||||
<button className="">
|
||||
{group.label ?? t('people_page.face_group.unlabeled', 'Unlabeled')}
|
||||
</button>
|
||||
{/* <EditIcon name="pencil" /> */}
|
||||
|
@ -138,9 +150,9 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
|||
)
|
||||
} else {
|
||||
label = (
|
||||
<FaceDetailsWrapper labeled={!!group.label}>
|
||||
<FaceDetailsWrapper className={className} labeled={!!group.label}>
|
||||
<TextField
|
||||
className="w-[160px]"
|
||||
className={textFieldClassName}
|
||||
loading={loading}
|
||||
ref={inputRef}
|
||||
// size="mini"
|
||||
|
@ -183,13 +195,20 @@ type FaceGroupProps = {
|
|||
|
||||
const FaceGroup = ({ group }: FaceGroupProps) => {
|
||||
const previewFace = group.imageFaces[0]
|
||||
const [editLabel, setEditLabel] = useState(false)
|
||||
|
||||
return (
|
||||
<div style={{ margin: '12px' }}>
|
||||
<Link to={`/people/${group.id}`}>
|
||||
<FaceCircleImage imageFace={previewFace} selectable />
|
||||
</Link>
|
||||
<FaceDetails group={group} />
|
||||
<FaceDetails
|
||||
className="block cursor-pointer text-center w-full mt-3"
|
||||
textFieldClassName="w-[140px]"
|
||||
group={group}
|
||||
editLabel={editLabel}
|
||||
setEditLabel={setEditLabel}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { gql, useMutation } from '@apollo/client'
|
||||
import { BaseMutationOptions, gql, useMutation } from '@apollo/client'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
@ -28,16 +28,50 @@ const DETACH_IMAGE_FACES_MUTATION = gql`
|
|||
}
|
||||
`
|
||||
|
||||
export const useDetachImageFaces = (
|
||||
mutationOptions: BaseMutationOptions<
|
||||
detachImageFaces,
|
||||
detachImageFacesVariables
|
||||
>
|
||||
) => {
|
||||
const [detachImageFacesMutation] = useMutation<
|
||||
detachImageFaces,
|
||||
detachImageFacesVariables
|
||||
>(DETACH_IMAGE_FACES_MUTATION, mutationOptions)
|
||||
|
||||
return async (
|
||||
selectedImageFaces: (
|
||||
| myFaces_myFaceGroups_imageFaces
|
||||
| singleFaceGroup_faceGroup_imageFaces
|
||||
)[]
|
||||
) => {
|
||||
const faceIDs = selectedImageFaces.map(face => face.id)
|
||||
|
||||
const result = await detachImageFacesMutation({
|
||||
variables: {
|
||||
faceIDs,
|
||||
},
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
type DetachImageFacesModalProps = {
|
||||
open: boolean
|
||||
setOpen(open: boolean): void
|
||||
faceGroup: myFaces_myFaceGroups | singleFaceGroup_faceGroup
|
||||
selectedImageFaces?: (
|
||||
| myFaces_myFaceGroups_imageFaces
|
||||
| singleFaceGroup_faceGroup_imageFaces
|
||||
)[]
|
||||
}
|
||||
|
||||
const DetachImageFacesModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
faceGroup,
|
||||
selectedImageFaces: selectedImageFacesProp,
|
||||
}: DetachImageFacesModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -46,10 +80,7 @@ const DetachImageFacesModal = ({
|
|||
>([])
|
||||
const history = useHistory()
|
||||
|
||||
const [detachImageFacesMutation] = useMutation<
|
||||
detachImageFaces,
|
||||
detachImageFacesVariables
|
||||
>(DETACH_IMAGE_FACES_MUTATION, {
|
||||
const detachImageFacesMutation = useDetachImageFaces({
|
||||
refetchQueries: [
|
||||
{
|
||||
query: MY_FACES_QUERY,
|
||||
|
@ -57,6 +88,19 @@ const DetachImageFacesModal = ({
|
|||
],
|
||||
})
|
||||
|
||||
const detachImageFaces = () => {
|
||||
detachImageFacesMutation(selectedImageFaces).then(({ data }) => {
|
||||
if (isNil(data)) throw new Error('Expected data not to be null')
|
||||
setOpen(false)
|
||||
history.push(`/people/${data.detachImageFaces.id}`)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(selectedImageFacesProp)) return
|
||||
setSelectedImageFaces(selectedImageFacesProp)
|
||||
}, [selectedImageFacesProp])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setSelectedImageFaces([])
|
||||
|
@ -65,20 +109,6 @@ const DetachImageFacesModal = ({
|
|||
|
||||
if (open == false) return null
|
||||
|
||||
const detachImageFaces = () => {
|
||||
const faceIDs = selectedImageFaces.map(face => face.id)
|
||||
|
||||
detachImageFacesMutation({
|
||||
variables: {
|
||||
faceIDs,
|
||||
},
|
||||
}).then(({ data }) => {
|
||||
if (isNil(data)) throw new Error('Expected data not to be null')
|
||||
setOpen(false)
|
||||
history.push(`/people/${data.detachImageFaces.id}`)
|
||||
})
|
||||
}
|
||||
|
||||
const imageFaces = faceGroup?.imageFaces ?? []
|
||||
|
||||
return (
|
||||
|
@ -94,7 +124,7 @@ const DetachImageFacesModal = ({
|
|||
actions={[
|
||||
{
|
||||
key: 'cancel',
|
||||
label: 'Cancel',
|
||||
label: t('general.action.cancel', 'Cancel'),
|
||||
onClick: () => setOpen(false),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, {
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { isNil } from '../../../helpers/utils'
|
||||
import { Button, TextField } from '../../../primitives/form/Input'
|
||||
import { SET_GROUP_LABEL_MUTATION } from '../PeoplePage'
|
||||
import { MY_FACES_QUERY, SET_GROUP_LABEL_MUTATION } from '../PeoplePage'
|
||||
import {
|
||||
setGroupLabel,
|
||||
setGroupLabelVariables,
|
||||
|
@ -112,6 +112,11 @@ const FaceGroupTitle = ({ faceGroup }: FaceGroupTitleProps) => {
|
|||
open={mergeModalOpen}
|
||||
setOpen={setMergeModalOpen}
|
||||
sourceFaceGroup={faceGroup}
|
||||
refetchQueries={[
|
||||
{
|
||||
query: MY_FACES_QUERY,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<MoveImageFacesModal
|
||||
open={moveModalOpen}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||
import { gql, PureQueryOptions, useMutation, useQuery } from '@apollo/client'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
@ -31,18 +31,24 @@ const COMBINE_FACES_MUTATION = gql`
|
|||
type MergeFaceGroupsModalProps = {
|
||||
open: boolean
|
||||
setOpen(open: boolean): void
|
||||
sourceFaceGroup: myFaces_myFaceGroups | singleFaceGroup_faceGroup
|
||||
sourceFaceGroup: {
|
||||
__typename: 'FaceGroup'
|
||||
id: string
|
||||
}
|
||||
refetchQueries: PureQueryOptions[]
|
||||
}
|
||||
|
||||
const MergeFaceGroupsModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
sourceFaceGroup,
|
||||
refetchQueries,
|
||||
}: MergeFaceGroupsModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [selectedFaceGroup, setSelectedFaceGroup] =
|
||||
useState<myFaces_myFaceGroups | singleFaceGroup_faceGroup | null>(null)
|
||||
const [selectedFaceGroup, setSelectedFaceGroup] = useState<
|
||||
myFaces_myFaceGroups | singleFaceGroup_faceGroup | null
|
||||
>(null)
|
||||
|
||||
const history = useHistory()
|
||||
const { data } = useQuery<myFaces, myFacesVariables>(MY_FACES_QUERY)
|
||||
|
@ -50,11 +56,7 @@ const MergeFaceGroupsModal = ({
|
|||
combineFaces,
|
||||
combineFacesVariables
|
||||
>(COMBINE_FACES_MUTATION, {
|
||||
refetchQueries: [
|
||||
{
|
||||
query: MY_FACES_QUERY,
|
||||
},
|
||||
],
|
||||
refetchQueries: refetchQueries,
|
||||
})
|
||||
|
||||
if (open == false) return null
|
||||
|
|
|
@ -40,20 +40,26 @@ type MoveImageFacesModalProps = {
|
|||
open: boolean
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
faceGroup: singleFaceGroup_faceGroup
|
||||
preselectedImageFaces?: (
|
||||
| singleFaceGroup_faceGroup_imageFaces
|
||||
| myFaces_myFaceGroups_imageFaces
|
||||
)[]
|
||||
}
|
||||
|
||||
const MoveImageFacesModal = ({
|
||||
open,
|
||||
setOpen,
|
||||
faceGroup,
|
||||
preselectedImageFaces,
|
||||
}: MoveImageFacesModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [selectedImageFaces, setSelectedImageFaces] = useState<
|
||||
(singleFaceGroup_faceGroup_imageFaces | myFaces_myFaceGroups_imageFaces)[]
|
||||
>([])
|
||||
const [selectedFaceGroup, setSelectedFaceGroup] =
|
||||
useState<myFaces_myFaceGroups | singleFaceGroup_faceGroup | null>(null)
|
||||
const [selectedFaceGroup, setSelectedFaceGroup] = useState<
|
||||
myFaces_myFaceGroups | singleFaceGroup_faceGroup | null
|
||||
>(null)
|
||||
const [imagesSelected, setImagesSelected] = useState(false)
|
||||
const history = useHistory()
|
||||
|
||||
|
@ -68,8 +74,16 @@ const MoveImageFacesModal = ({
|
|||
],
|
||||
})
|
||||
|
||||
const [loadFaceGroups, { data: faceGroupsData }] =
|
||||
useLazyQuery<myFaces, myFacesVariables>(MY_FACES_QUERY)
|
||||
const [loadFaceGroups, { data: faceGroupsData }] = useLazyQuery<
|
||||
myFaces,
|
||||
myFacesVariables
|
||||
>(MY_FACES_QUERY)
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(preselectedImageFaces)) return
|
||||
setSelectedImageFaces(preselectedImageFaces)
|
||||
setImagesSelected(true)
|
||||
}, [preselectedImageFaces])
|
||||
|
||||
useEffect(() => {
|
||||
if (imagesSelected) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
} from './__generated__/sidebarMediaQuery'
|
||||
import { BreadcrumbList } from '../../album/AlbumTitle'
|
||||
|
||||
const SIDEBAR_MEDIA_QUERY = gql`
|
||||
export const SIDEBAR_MEDIA_QUERY = gql`
|
||||
query sidebarMediaQuery($id: ID!) {
|
||||
media(id: $id) {
|
||||
id
|
||||
|
@ -99,6 +99,7 @@ const SIDEBAR_MEDIA_QUERY = gql`
|
|||
faceGroup {
|
||||
id
|
||||
label
|
||||
imageFaceCount
|
||||
}
|
||||
media {
|
||||
id
|
||||
|
|
|
@ -1,34 +1,46 @@
|
|||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import FaceCircleImage from '../../../Pages/PeoplePage/FaceCircleImage'
|
||||
import { SidebarSection, SidebarSectionTitle } from '../SidebarComponents'
|
||||
import { MediaSidebarMedia } from './MediaSidebar'
|
||||
import { MediaSidebarMedia, SIDEBAR_MEDIA_QUERY } from './MediaSidebar'
|
||||
import { sidebarMediaQuery_media_faces } from './__generated__/sidebarMediaQuery'
|
||||
|
||||
import { ReactComponent as PeopleDotsIcon } from './icons/peopleDotsIcon.svg'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { Button } from '../../../primitives/form/Input'
|
||||
import { ArrowPopoverPanel } from '../Sharing'
|
||||
import { tailwindClassNames } from '../../../helpers/utils'
|
||||
import { isNil, tailwindClassNames } from '../../../helpers/utils'
|
||||
import MergeFaceGroupsModal from '../../../Pages/PeoplePage/SingleFaceGroup/MergeFaceGroupsModal'
|
||||
import { useDetachImageFaces } from '../../../Pages/PeoplePage/SingleFaceGroup/DetachImageFacesModal'
|
||||
import MoveImageFacesModal from '../../../Pages/PeoplePage/SingleFaceGroup/MoveImageFacesModal'
|
||||
import { FaceDetails } from '../../../Pages/PeoplePage/PeoplePage'
|
||||
|
||||
type PersonMoreMenuItemProps = {
|
||||
label: string
|
||||
className?: string
|
||||
onClick(): void
|
||||
}
|
||||
|
||||
const PersonMoreMenuItem = ({ label, className }: PersonMoreMenuItemProps) => {
|
||||
const PersonMoreMenuItem = ({
|
||||
label,
|
||||
className,
|
||||
onClick,
|
||||
}: PersonMoreMenuItemProps) => {
|
||||
return (
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={tailwindClassNames(
|
||||
`block py-1 cursor-pointer ${active && 'bg-gray-50 text-black'}`,
|
||||
`whitespace-normal w-full block py-1 cursor-pointer ${
|
||||
active ? 'bg-gray-50 text-black' : 'text-gray-700'
|
||||
}`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
)
|
||||
|
@ -36,40 +48,105 @@ const PersonMoreMenuItem = ({ label, className }: PersonMoreMenuItemProps) => {
|
|||
|
||||
type PersonMoreMenuProps = {
|
||||
face: sidebarMediaQuery_media_faces
|
||||
setChangeLabel: React.Dispatch<React.SetStateAction<boolean>>
|
||||
className?: string
|
||||
}
|
||||
|
||||
const PersonMoreMenu = ({ face }: PersonMoreMenuProps) => {
|
||||
const PersonMoreMenu = ({
|
||||
face,
|
||||
setChangeLabel,
|
||||
className,
|
||||
}: PersonMoreMenuProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
face
|
||||
const [mergeModalOpen, setMergeModalOpen] = useState(false)
|
||||
const [moveModalOpen, setMoveModalOpen] = useState(false)
|
||||
|
||||
const refetchQueries = [
|
||||
{
|
||||
query: SIDEBAR_MEDIA_QUERY,
|
||||
variables: {
|
||||
id: face.media.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const history = useHistory()
|
||||
const detachImageFaceMutation = useDetachImageFaces({
|
||||
refetchQueries,
|
||||
})
|
||||
|
||||
const modals = (
|
||||
<>
|
||||
<MergeFaceGroupsModal
|
||||
sourceFaceGroup={face.faceGroup}
|
||||
open={mergeModalOpen}
|
||||
setOpen={setMergeModalOpen}
|
||||
refetchQueries={refetchQueries}
|
||||
/>
|
||||
<MoveImageFacesModal
|
||||
faceGroup={{ imageFaces: [], ...face.faceGroup }}
|
||||
open={moveModalOpen}
|
||||
setOpen={setMoveModalOpen}
|
||||
preselectedImageFaces={[face]}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
const detachImageFace = () => {
|
||||
if (
|
||||
!confirm(
|
||||
t(
|
||||
'sidebar.people.confirm_image_detach',
|
||||
'Are you sure you want to detach this image?'
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
detachImageFaceMutation([face]).then(({ data }) => {
|
||||
if (isNil(data)) throw new Error('Expected data not to be null')
|
||||
history.push(`/people/${data.detachImageFaces.id}`)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block">
|
||||
<Menu.Button as={Button} className="px-1.5 py-1.5 align-middle ml-1">
|
||||
<PeopleDotsIcon className="text-gray-500" />
|
||||
</Menu.Button>
|
||||
<Menu.Items className="">
|
||||
<ArrowPopoverPanel width={120}>
|
||||
<PersonMoreMenuItem
|
||||
className="border-b"
|
||||
label={t('people_page.action_label.change_label', 'Change label')}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
className="border-b"
|
||||
label={t('sidebar.people.action_label.merge_face', 'Merge face')}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
className="border-b"
|
||||
label={t(
|
||||
'sidebar.people.action_label.detach_image',
|
||||
'Detach image'
|
||||
)}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
label={t('sidebar.people.action_label.move_face', 'Move face')}
|
||||
/>
|
||||
</ArrowPopoverPanel>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
<>
|
||||
<Menu
|
||||
as="div"
|
||||
className={tailwindClassNames('relative inline-block', className)}
|
||||
>
|
||||
<Menu.Button as={Button} className="px-1.5 py-1.5 align-middle ml-1">
|
||||
<PeopleDotsIcon className="text-gray-500" />
|
||||
</Menu.Button>
|
||||
<Menu.Items className="">
|
||||
<ArrowPopoverPanel width={120}>
|
||||
<PersonMoreMenuItem
|
||||
onClick={() => setChangeLabel(true)}
|
||||
className="border-b"
|
||||
label={t('people_page.action_label.change_label', 'Change label')}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
onClick={() => setMergeModalOpen(true)}
|
||||
className="border-b"
|
||||
label={t('sidebar.people.action_label.merge_face', 'Merge face')}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
onClick={() => detachImageFace()}
|
||||
className="border-b"
|
||||
label={t(
|
||||
'sidebar.people.action_label.detach_image',
|
||||
'Detach image'
|
||||
)}
|
||||
/>
|
||||
<PersonMoreMenuItem
|
||||
onClick={() => setMoveModalOpen(true)}
|
||||
label={t('sidebar.people.action_label.move_face', 'Move face')}
|
||||
/>
|
||||
</ArrowPopoverPanel>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
{modals}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -78,21 +155,28 @@ type MediaSidebarFaceProps = {
|
|||
}
|
||||
|
||||
const MediaSidebarPerson = ({ face }: MediaSidebarFaceProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [changeLabel, setChangeLabel] = useState(false)
|
||||
|
||||
return (
|
||||
<li className="inline-block">
|
||||
<Link to={`/people/${face.faceGroup.id}`}>
|
||||
<FaceCircleImage imageFace={face} selectable={true} size="92px" />
|
||||
</Link>
|
||||
<div
|
||||
className={`text-center text-sm mt-1 ${
|
||||
face.faceGroup.label ? 'text-black' : 'text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{face.faceGroup.label ??
|
||||
t('people_page.face_group.unlabeled', 'Unlabeled')}
|
||||
<PersonMoreMenu face={face} />
|
||||
<div className="mt-1 whitespace-nowrap">
|
||||
<FaceDetails
|
||||
className="text-sm max-w-[80px] align-middle"
|
||||
textFieldClassName="w-[100px]"
|
||||
group={face.faceGroup}
|
||||
editLabel={changeLabel}
|
||||
setEditLabel={setChangeLabel}
|
||||
/>
|
||||
{!changeLabel && (
|
||||
<PersonMoreMenu
|
||||
className="pl-0.5"
|
||||
face={face}
|
||||
setChangeLabel={setChangeLabel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
|
|
|
@ -153,6 +153,7 @@ export interface sidebarMediaQuery_media_faces_faceGroup {
|
|||
__typename: 'FaceGroup'
|
||||
id: string
|
||||
label: string | null
|
||||
imageFaceCount: number
|
||||
}
|
||||
|
||||
export interface sidebarMediaQuery_media_faces_media_thumbnail {
|
||||
|
|
|
@ -263,7 +263,7 @@
|
|||
"title": "Lokation"
|
||||
},
|
||||
"media": {
|
||||
"album_path": "",
|
||||
"album_path": "Album sti",
|
||||
"exif": {
|
||||
"exposure_program": {
|
||||
"action_program": "Actionprogram",
|
||||
|
@ -306,10 +306,11 @@
|
|||
},
|
||||
"people": {
|
||||
"action_label": {
|
||||
"detach_image": "",
|
||||
"merge_face": "",
|
||||
"move_face": ""
|
||||
"detach_image": "Løsriv billede",
|
||||
"merge_face": "Sammenflet person",
|
||||
"move_face": "Flyt person"
|
||||
},
|
||||
"confirm_image_detach": "Er du sikker på at du vil løsrive dette billede?",
|
||||
"title": "Personer"
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "Merge face",
|
||||
"move_face": "Move face"
|
||||
},
|
||||
"confirm_image_detach": "Are you sure you want to detach this image?",
|
||||
"title": "People"
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -315,6 +315,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -315,6 +315,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -310,6 +310,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -305,6 +305,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -305,6 +305,7 @@
|
|||
"merge_face": "",
|
||||
"move_face": ""
|
||||
},
|
||||
"confirm_image_detach": "",
|
||||
"title": ""
|
||||
},
|
||||
"sharing": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import classNames, { Argument as ClassNamesArg } from 'classnames'
|
||||
import { overrideTailwindClasses } from 'tailwind-override'
|
||||
// import { overrideTailwindClasses } from 'tailwind-override'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export interface DebouncedFn<F extends (...args: any[]) => any> {
|
||||
|
@ -45,5 +45,6 @@ export function exhaustiveCheck(value: never) {
|
|||
}
|
||||
|
||||
export function tailwindClassNames(...args: ClassNamesArg[]) {
|
||||
return overrideTailwindClasses(classNames(args))
|
||||
// return overrideTailwindClasses(classNames(args))
|
||||
return classNames(args)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue