mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web): suggest to merge people faces when renaming a person name (#3399)
* feat: propose to merge faced based on the name * responsive * drop down menu * add border * improvements * improvements * improvements * add comments * responsive * responsive * feat: use FullScreenModal * responsive * pr feeback * pr feeback * pr feeback * responsive * pr feeback * pr feeback * styling * fix test --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
		| @@ -14,6 +14,7 @@ | |||||||
|   export let shadow = false; |   export let shadow = false; | ||||||
|   export let circle = false; |   export let circle = false; | ||||||
|   export let hidden = false; |   export let hidden = false; | ||||||
|  |   export let border = false; | ||||||
|   let complete = false; |   let complete = false; | ||||||
|  |  | ||||||
|   export let eyeColor = 'white'; |   export let eyeColor = 'white'; | ||||||
| @@ -26,7 +27,9 @@ | |||||||
|   style:opacity={hidden ? '0.5' : '1'} |   style:opacity={hidden ? '0.5' : '1'} | ||||||
|   src={url} |   src={url} | ||||||
|   alt={altText} |   alt={altText} | ||||||
|   class="object-cover transition duration-300" |   class="object-cover transition duration-300 {border | ||||||
|  |     ? 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary' | ||||||
|  |     : ''}" | ||||||
|   class:rounded-lg={curve} |   class:rounded-lg={curve} | ||||||
|   class:shadow-lg={shadow} |   class:shadow-lg={shadow} | ||||||
|   class:rounded-full={circle} |   class:rounded-full={circle} | ||||||
|   | |||||||
							
								
								
									
										128
									
								
								web/src/lib/components/faces-page/merge-suggestion-modal.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								web/src/lib/components/faces-page/merge-suggestion-modal.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  |   import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; | ||||||
|  |   import { createEventDispatcher } from 'svelte'; | ||||||
|  |   import Close from 'svelte-material-icons/Close.svelte'; | ||||||
|  |   import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; | ||||||
|  |   import type { PersonResponseDto } from '../../../api/open-api'; | ||||||
|  |   import { api } from '@api'; | ||||||
|  |   import Merge from 'svelte-material-icons/Merge.svelte'; | ||||||
|  |   import Button from '../elements/buttons/button.svelte'; | ||||||
|  |   import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; | ||||||
|  |  | ||||||
|  |   const dispatch = createEventDispatcher<{ | ||||||
|  |     reject: void; | ||||||
|  |     confirm: [PersonResponseDto, PersonResponseDto]; | ||||||
|  |     close: void; | ||||||
|  |   }>(); | ||||||
|  |  | ||||||
|  |   export let personMerge1: PersonResponseDto; | ||||||
|  |   export let personMerge2: PersonResponseDto; | ||||||
|  |   export let people: PersonResponseDto[]; | ||||||
|  |   let potentialMergePeople: PersonResponseDto[] = people | ||||||
|  |     .filter( | ||||||
|  |       (person: PersonResponseDto) => | ||||||
|  |         personMerge2.name.toLowerCase() === person.name.toLowerCase() && | ||||||
|  |         person.id !== personMerge2.id && | ||||||
|  |         person.id !== personMerge1.id && | ||||||
|  |         !person.isHidden, | ||||||
|  |     ) | ||||||
|  |     .slice(0, 3); | ||||||
|  |  | ||||||
|  |   let choosePersonToMerge = false; | ||||||
|  |  | ||||||
|  |   const title = personMerge2.name; | ||||||
|  |  | ||||||
|  |   const changePersonToMerge = (newperson: PersonResponseDto) => { | ||||||
|  |     const index = potentialMergePeople.indexOf(newperson); | ||||||
|  |     [potentialMergePeople[index], personMerge2] = [personMerge2, potentialMergePeople[index]]; | ||||||
|  |     choosePersonToMerge = false; | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="flex h-full w-full place-content-center place-items-center overflow-hidden"> | ||||||
|  |   <div | ||||||
|  |     class="w-[250px] max-w-[125vw] rounded-3xl border bg-immich-bg shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg md:w-[375px]" | ||||||
|  |   > | ||||||
|  |     <div class="relative flex items-center justify-between"> | ||||||
|  |       <h1 class="truncate px-4 py-4 font-medium text-immich-primary dark:text-immich-dark-primary"> | ||||||
|  |         Merge faces - {title} | ||||||
|  |       </h1> | ||||||
|  |       <CircleIconButton logo={Close} on:click={() => dispatch('close')} /> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="flex items-center justify-center px-2 py-4 md:h-36 md:px-4 md:py-4"> | ||||||
|  |       {#if !choosePersonToMerge} | ||||||
|  |         <div class="flex h-20 w-20 items-center px-1 md:h-24 md:w-24 md:px-2"> | ||||||
|  |           <ImageThumbnail | ||||||
|  |             circle | ||||||
|  |             shadow | ||||||
|  |             url={api.getPeopleThumbnailUrl(personMerge1.id)} | ||||||
|  |             altText={personMerge1.name} | ||||||
|  |             widthStyle="100%" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |         <div class="mx-0.5 flex md:mx-2"> | ||||||
|  |           <CircleIconButton | ||||||
|  |             logo={Merge} | ||||||
|  |             on:click={() => ([personMerge1, personMerge2] = [personMerge2, personMerge1])} | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <button | ||||||
|  |           disabled={potentialMergePeople.length === 0} | ||||||
|  |           class="flex h-28 w-28 items-center rounded-full border-2 border-immich-primary px-1 dark:border-immich-dark-primary md:h-32 md:w-32 md:px-2" | ||||||
|  |           on:click={() => { | ||||||
|  |             if (potentialMergePeople.length > 0) { | ||||||
|  |               choosePersonToMerge = !choosePersonToMerge; | ||||||
|  |             } | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           <ImageThumbnail | ||||||
|  |             border={potentialMergePeople.length !== 0} | ||||||
|  |             circle | ||||||
|  |             shadow | ||||||
|  |             url={api.getPeopleThumbnailUrl(personMerge2.id)} | ||||||
|  |             altText={personMerge2.name} | ||||||
|  |             widthStyle="100%" | ||||||
|  |           /> | ||||||
|  |         </button> | ||||||
|  |       {:else} | ||||||
|  |         <div class="grid w-full grid-cols-1 gap-2"> | ||||||
|  |           <div class="px-2"> | ||||||
|  |             <button on:click={() => (choosePersonToMerge = false)}> <ArrowLeft /></button> | ||||||
|  |           </div> | ||||||
|  |           <div class="flex items-center justify-center"> | ||||||
|  |             <div class="flex flex-wrap justify-center md:grid md:grid-cols-{potentialMergePeople.length}"> | ||||||
|  |               {#each potentialMergePeople as person (person.id)} | ||||||
|  |                 <div class="h-24 w-24 md:h-28 md:w-28"> | ||||||
|  |                   <button class="p-2" on:click={() => changePersonToMerge(person)}> | ||||||
|  |                     <ImageThumbnail | ||||||
|  |                       border={true} | ||||||
|  |                       circle | ||||||
|  |                       shadow | ||||||
|  |                       url={api.getPeopleThumbnailUrl(person.id)} | ||||||
|  |                       altText={person.name} | ||||||
|  |                       widthStyle="100%" | ||||||
|  |                       on:click={() => changePersonToMerge(person)} | ||||||
|  |                     /> | ||||||
|  |                   </button> | ||||||
|  |                 </div> | ||||||
|  |               {/each} | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       {/if} | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="flex px-4 md:px-8 md:pt-4"> | ||||||
|  |       <h1 class="text-xl text-gray-500 dark:text-gray-300">Are these the same face?</h1> | ||||||
|  |     </div> | ||||||
|  |     <div class="flex px-4 pt-2 md:px-8"> | ||||||
|  |       <p class="text-sm text-gray-500 dark:text-gray-300">They will be merged together</p> | ||||||
|  |     </div> | ||||||
|  |     <div class="mt-8 flex w-full gap-4 px-4 pb-4"> | ||||||
|  |       <Button color="gray" fullwidth on:click={() => dispatch('reject')}>No</Button> | ||||||
|  |       <Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>Yes</Button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
| @@ -19,6 +19,7 @@ | |||||||
|   import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; |   import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; | ||||||
|   import { onDestroy, onMount } from 'svelte'; |   import { onDestroy, onMount } from 'svelte'; | ||||||
|   import { browser } from '$app/environment'; |   import { browser } from '$app/environment'; | ||||||
|  |   import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte'; | ||||||
|  |  | ||||||
|   export let data: PageData; |   export let data: PageData; | ||||||
|   let selectHidden = false; |   let selectHidden = false; | ||||||
| @@ -33,6 +34,13 @@ | |||||||
|   let showLoadingSpinner = false; |   let showLoadingSpinner = false; | ||||||
|   let toggleVisibility = false; |   let toggleVisibility = false; | ||||||
|  |  | ||||||
|  |   let showChangeNameModal = false; | ||||||
|  |   let showMergeModal = false; | ||||||
|  |   let personName = ''; | ||||||
|  |   let personMerge1: PersonResponseDto; | ||||||
|  |   let personMerge2: PersonResponseDto; | ||||||
|  |   let edittingPerson: PersonResponseDto | null = null; | ||||||
|  |  | ||||||
|   people.forEach((person: PersonResponseDto) => { |   people.forEach((person: PersonResponseDto) => { | ||||||
|     initialHiddenValues[person.id] = person.isHidden; |     initialHiddenValues[person.id] = person.isHidden; | ||||||
|   }); |   }); | ||||||
| @@ -136,13 +144,60 @@ | |||||||
|     toggleVisibility = false; |     toggleVisibility = false; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   let showChangeNameModal = false; |   const handleMergeSameFace = async (response: [PersonResponseDto, PersonResponseDto]) => { | ||||||
|   let personName = ''; |     const [personToMerge, personToBeMergedIn] = response; | ||||||
|   let edittingPerson: PersonResponseDto | null = null; |     showMergeModal = false; | ||||||
|  |  | ||||||
|  |     if (!edittingPerson) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |       await api.personApi.mergePerson({ | ||||||
|  |         id: personMerge2.id, | ||||||
|  |         mergePersonDto: { ids: [personToMerge.id] }, | ||||||
|  |       }); | ||||||
|  |       countVisiblePeople--; | ||||||
|  |       people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id); | ||||||
|  |  | ||||||
|  |       notificationController.show({ | ||||||
|  |         message: 'Merge faces succesfully', | ||||||
|  |         type: NotificationType.Info, | ||||||
|  |       }); | ||||||
|  |     } catch (error) { | ||||||
|  |       handleError(error, 'Unable to save name'); | ||||||
|  |     } | ||||||
|  |     if (personToBeMergedIn.name !== personName && edittingPerson.id === personToBeMergedIn.id) { | ||||||
|  |       /* | ||||||
|  |        * | ||||||
|  |        * If the user merges one of the suggested people into the person he's editing it, it's merging the suggested person AND renames | ||||||
|  |        * the person he's editing | ||||||
|  |        * | ||||||
|  |        */ | ||||||
|  |       try { | ||||||
|  |         await api.personApi.updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } }); | ||||||
|  |         for (const person of people) { | ||||||
|  |           if (person.id === personToBeMergedIn.id) { | ||||||
|  |             person.name = personName; | ||||||
|  |             break; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         notificationController.show({ | ||||||
|  |           message: 'Change name succesfully', | ||||||
|  |           type: NotificationType.Info, | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // trigger reactivity | ||||||
|  |         people = people; | ||||||
|  |       } catch (error) { | ||||||
|  |         handleError(error, 'Unable to save name'); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const handleChangeName = ({ detail }: CustomEvent<PersonResponseDto>) => { |   const handleChangeName = ({ detail }: CustomEvent<PersonResponseDto>) => { | ||||||
|     showChangeNameModal = true; |     showChangeNameModal = true; | ||||||
|     personName = detail.name; |     personName = detail.name; | ||||||
|  |     personMerge1 = detail; | ||||||
|     edittingPerson = detail; |     edittingPerson = detail; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -182,33 +237,73 @@ | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const submitNameChange = async () => { |   const submitNameChange = async () => { | ||||||
|  |     showChangeNameModal = false; | ||||||
|  |     if (!edittingPerson) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (personName === edittingPerson.name) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // We check if another person has the same name as the name entered by the user | ||||||
|  |  | ||||||
|  |     const existingPerson = people.find( | ||||||
|  |       (person: PersonResponseDto) => | ||||||
|  |         person.name.toLowerCase() === personName.toLowerCase() && | ||||||
|  |         edittingPerson && | ||||||
|  |         person.id !== edittingPerson.id && | ||||||
|  |         person.name, | ||||||
|  |     ); | ||||||
|  |     if (existingPerson) { | ||||||
|  |       personMerge2 = existingPerson; | ||||||
|  |       showMergeModal = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     changeName(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const changeName = async () => { | ||||||
|  |     showMergeModal = false; | ||||||
|  |     showChangeNameModal = false; | ||||||
|  |  | ||||||
|  |     if (!edittingPerson) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     try { |     try { | ||||||
|       if (edittingPerson) { |       const { data: updatedPerson } = await api.personApi.updatePerson({ | ||||||
|         const { data: updatedPerson } = await api.personApi.updatePerson({ |         id: edittingPerson.id, | ||||||
|           id: edittingPerson.id, |         personUpdateDto: { name: personName }, | ||||||
|           personUpdateDto: { name: personName }, |       }); | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         people = people.map((person: PersonResponseDto) => { |       people = people.map((person: PersonResponseDto) => { | ||||||
|           if (person.id === updatedPerson.id) { |         if (person.id === updatedPerson.id) { | ||||||
|             return updatedPerson; |           return updatedPerson; | ||||||
|           } |         } | ||||||
|           return person; |         return person; | ||||||
|         }); |       }); | ||||||
|  |  | ||||||
|         showChangeNameModal = false; |       notificationController.show({ | ||||||
|  |         message: 'Change name succesfully', | ||||||
|         notificationController.show({ |         type: NotificationType.Info, | ||||||
|           message: 'Change name succesfully', |       }); | ||||||
|           type: NotificationType.Info, |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       handleError(error, 'Unable to save name'); |       handleError(error, 'Unable to save name'); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | {#if showMergeModal} | ||||||
|  |   <FullScreenModal on:clickOutside={() => (showMergeModal = false)}> | ||||||
|  |     <MergeSuggestionModal | ||||||
|  |       {personMerge1} | ||||||
|  |       {personMerge2} | ||||||
|  |       {people} | ||||||
|  |       on:close={() => (showMergeModal = false)} | ||||||
|  |       on:reject={() => changeName()} | ||||||
|  |       on:confirm={(event) => handleMergeSameFace(event.detail)} | ||||||
|  |     /> | ||||||
|  |   </FullScreenModal> | ||||||
|  | {/if} | ||||||
|  |  | ||||||
| <UserPageLayout user={data.user} title="People"> | <UserPageLayout user={data.user} title="People"> | ||||||
|   <svelte:fragment slot="buttons"> |   <svelte:fragment slot="buttons"> | ||||||
|     {#if countTotalPeople > 0} |     {#if countTotalPeople > 0} | ||||||
|   | |||||||
| @@ -10,11 +10,13 @@ export const load = (async ({ locals, parent, params }) => { | |||||||
|  |  | ||||||
|   const { data: person } = await locals.api.personApi.getPerson({ id: params.personId }); |   const { data: person } = await locals.api.personApi.getPerson({ id: params.personId }); | ||||||
|   const { data: assets } = await locals.api.personApi.getPersonAssets({ id: params.personId }); |   const { data: assets } = await locals.api.personApi.getPersonAssets({ id: params.personId }); | ||||||
|  |   const { data: people } = await locals.api.personApi.getAllPeople({ withHidden: false }); | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     user, |     user, | ||||||
|     assets, |     assets, | ||||||
|     person, |     person, | ||||||
|  |     people, | ||||||
|     meta: { |     meta: { | ||||||
|       title: person.name || 'Person', |       title: person.name || 'Person', | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { afterNavigate, goto } from '$app/navigation'; |   import { afterNavigate, goto, invalidateAll } from '$app/navigation'; | ||||||
|   import { page } from '$app/stores'; |   import { page } from '$app/stores'; | ||||||
|   import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; |   import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; | ||||||
|   import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte'; |   import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte'; | ||||||
| @@ -15,7 +15,7 @@ | |||||||
|   import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte'; |   import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte'; | ||||||
|   import { AppRoute } from '$lib/constants'; |   import { AppRoute } from '$lib/constants'; | ||||||
|   import { handleError } from '$lib/utils/handle-error'; |   import { handleError } from '$lib/utils/handle-error'; | ||||||
|   import { AssetResponseDto, api } from '@api'; |   import { AssetResponseDto, PersonResponseDto, api } from '@api'; | ||||||
|   import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; |   import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; | ||||||
|   import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; |   import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; | ||||||
|   import Plus from 'svelte-material-icons/Plus.svelte'; |   import Plus from 'svelte-material-icons/Plus.svelte'; | ||||||
| @@ -30,6 +30,8 @@ | |||||||
|   } from '$lib/components/shared-components/notification/notification'; |   } from '$lib/components/shared-components/notification/notification'; | ||||||
|   import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte'; |   import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte'; | ||||||
|   import { onMount } from 'svelte'; |   import { onMount } from 'svelte'; | ||||||
|  |   import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte'; | ||||||
|  |   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | ||||||
|  |  | ||||||
|   export let data: PageData; |   export let data: PageData; | ||||||
|   let isEditingName = false; |   let isEditingName = false; | ||||||
| @@ -37,6 +39,13 @@ | |||||||
|   let showMergeFacePanel = false; |   let showMergeFacePanel = false; | ||||||
|   let previousRoute: string = AppRoute.EXPLORE; |   let previousRoute: string = AppRoute.EXPLORE; | ||||||
|   let selectedAssets: Set<AssetResponseDto> = new Set(); |   let selectedAssets: Set<AssetResponseDto> = new Set(); | ||||||
|  |   let showMergeModal = false; | ||||||
|  |   let people = data.people.people; | ||||||
|  |   let personMerge1: PersonResponseDto; | ||||||
|  |   let personMerge2: PersonResponseDto; | ||||||
|  |  | ||||||
|  |   let personName = ''; | ||||||
|  |  | ||||||
|   $: isMultiSelectionMode = selectedAssets.size > 0; |   $: isMultiSelectionMode = selectedAssets.size > 0; | ||||||
|   $: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived); |   $: isAllArchive = Array.from(selectedAssets).every((asset) => asset.isArchived); | ||||||
|   $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite); |   $: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite); | ||||||
| @@ -56,16 +65,6 @@ | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const handleNameChange = async (name: string) => { |  | ||||||
|     try { |  | ||||||
|       isEditingName = false; |  | ||||||
|       data.person.name = name; |  | ||||||
|       await api.personApi.updatePerson({ id: data.person.id, personUpdateDto: { name } }); |  | ||||||
|     } catch (error) { |  | ||||||
|       handleError(error, 'Unable to save name'); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const onAssetDelete = (assetId: string) => { |   const onAssetDelete = (assetId: string) => { | ||||||
|     data.assets = data.assets.filter((asset: AssetResponseDto) => asset.id !== assetId); |     data.assets = data.assets.filter((asset: AssetResponseDto) => asset.id !== assetId); | ||||||
|   }; |   }; | ||||||
| @@ -91,8 +90,92 @@ | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|  |   const handleMergeSameFace = async (response: [PersonResponseDto, PersonResponseDto]) => { | ||||||
|  |     const [personToMerge, personToBeMergedIn] = response; | ||||||
|  |     showMergeModal = false; | ||||||
|  |     try { | ||||||
|  |       await api.personApi.mergePerson({ | ||||||
|  |         id: personToBeMergedIn.id, | ||||||
|  |         mergePersonDto: { ids: [personToMerge.id] }, | ||||||
|  |       }); | ||||||
|  |       notificationController.show({ | ||||||
|  |         message: 'Merge faces succesfully', | ||||||
|  |         type: NotificationType.Info, | ||||||
|  |       }); | ||||||
|  |       people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id); | ||||||
|  |       if (personToBeMergedIn.name != personName && data.person.id === personToBeMergedIn.id) { | ||||||
|  |         changeName(); | ||||||
|  |         invalidateAll(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true }); | ||||||
|  |     } catch (error) { | ||||||
|  |       handleError(error, 'Unable to save name'); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const changeName = async () => { | ||||||
|  |     showMergeModal = false; | ||||||
|  |     data.person.name = personName; | ||||||
|  |     try { | ||||||
|  |       isEditingName = false; | ||||||
|  |  | ||||||
|  |       const { data: updatedPerson } = await api.personApi.updatePerson({ | ||||||
|  |         id: data.person.id, | ||||||
|  |         personUpdateDto: { name: personName }, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       people = people.map((person: PersonResponseDto) => { | ||||||
|  |         if (person.id === updatedPerson.id) { | ||||||
|  |           return updatedPerson; | ||||||
|  |         } | ||||||
|  |         return person; | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       notificationController.show({ | ||||||
|  |         message: 'Change name succesfully', | ||||||
|  |         type: NotificationType.Info, | ||||||
|  |       }); | ||||||
|  |     } catch (error) { | ||||||
|  |       handleError(error, 'Unable to save name'); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const handleNameChange = async (name: string) => { | ||||||
|  |     personName = name; | ||||||
|  |  | ||||||
|  |     if (data.person.name === personName) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const existingPerson = people.find( | ||||||
|  |       (person: PersonResponseDto) => | ||||||
|  |         person.name.toLowerCase() === personName.toLowerCase() && person.id !== data.person.id && person.name, | ||||||
|  |     ); | ||||||
|  |     if (existingPerson) { | ||||||
|  |       personMerge2 = existingPerson; | ||||||
|  |       personMerge1 = data.person; | ||||||
|  |       showMergeModal = true; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     changeName(); | ||||||
|  |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | {#if showMergeModal} | ||||||
|  |   <FullScreenModal on:clickOutside={() => (showMergeModal = false)}> | ||||||
|  |     <MergeSuggestionModal | ||||||
|  |       {personMerge1} | ||||||
|  |       {personMerge2} | ||||||
|  |       {people} | ||||||
|  |       on:close={() => (showMergeModal = false)} | ||||||
|  |       on:reject={() => changeName()} | ||||||
|  |       on:confirm={(event) => handleMergeSameFace(event.detail)} | ||||||
|  |     /> | ||||||
|  |   </FullScreenModal> | ||||||
|  | {/if} | ||||||
|  |  | ||||||
| {#if isMultiSelectionMode} | {#if isMultiSelectionMode} | ||||||
|   <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}> |   <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}> | ||||||
|     <CreateSharedLink /> |     <CreateSharedLink /> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user