refactor: person merge suggestion modal (#18287)
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
import { page } from '$app/stores';
|
||||
import { focusTrap } from '$lib/actions/focus-trap';
|
||||
import { scrollMemory } from '$lib/actions/scroll-memory';
|
||||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import ManagePeopleVisibility from '$lib/components/faces-page/manage-people-visibility.svelte';
|
||||
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
||||
import PeopleCard from '$lib/components/faces-page/people-card.svelte';
|
||||
import PeopleInfiniteScroll from '$lib/components/faces-page/people-infinite-scroll.svelte';
|
||||
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
|
||||
@@ -17,19 +17,13 @@
|
||||
import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
|
||||
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { websocketEvents } from '$lib/stores/websocket';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { clearQueryParam } from '$lib/utils/navigation';
|
||||
import {
|
||||
getAllPeople,
|
||||
getPerson,
|
||||
mergePerson,
|
||||
searchPerson,
|
||||
updatePerson,
|
||||
type PersonResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { getAllPeople, getPerson, searchPerson, updatePerson, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button } from '@immich/ui';
|
||||
import { mdiAccountOff, mdiEyeOutline } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -46,7 +40,6 @@
|
||||
|
||||
let selectHidden = $state(false);
|
||||
let searchName = $state('');
|
||||
let showMergeModal = $state(false);
|
||||
let newName = $state('');
|
||||
let currentPage = $state(1);
|
||||
let nextPage = $state(data.people.hasNextPage ? 2 : null);
|
||||
@@ -131,42 +124,41 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleMergeSamePerson = async (response: [PersonResponseDto, PersonResponseDto]) => {
|
||||
const [personToMerge, personToBeMergedIn] = response;
|
||||
showMergeModal = false;
|
||||
|
||||
if (!editingPerson) {
|
||||
const handleMerge = async () => {
|
||||
if (!editingPerson || !personMerge1 || !personMerge2) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await mergePerson({
|
||||
id: personToBeMergedIn.id,
|
||||
mergePersonDto: { ids: [personToMerge.id] },
|
||||
});
|
||||
|
||||
const mergedPerson = await getPerson({ id: personToBeMergedIn.id });
|
||||
const response = await modalManager.show(PersonMergeSuggestionModal, {
|
||||
personToMerge: personMerge1,
|
||||
personToBeMergedInto: personMerge2,
|
||||
potentialMergePeople,
|
||||
});
|
||||
|
||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||
people = people.map((person: PersonResponseDto) => (person.id === personToBeMergedIn.id ? mergedPerson : person));
|
||||
notificationController.show({
|
||||
message: $t('merge_people_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_name'));
|
||||
if (!response) {
|
||||
await updateName(personMerge1.id, newName);
|
||||
return;
|
||||
}
|
||||
if (personToBeMergedIn.name !== newName && editingPerson.id === personToBeMergedIn.id) {
|
||||
|
||||
const [personToMerge, personToBeMergedInto] = response;
|
||||
|
||||
const mergedPerson = await getPerson({ id: personToBeMergedInto.id });
|
||||
|
||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||
people = people.map((person: PersonResponseDto) => (person.id === personToBeMergedInto.id ? mergedPerson : person));
|
||||
|
||||
if (personToBeMergedInto.name !== newName && editingPerson.id === personToBeMergedInto.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
|
||||
* If the user merges one of the suggested people into the person he's editing, it's merging the suggested person AND renames
|
||||
* the person he's editing
|
||||
*
|
||||
*/
|
||||
try {
|
||||
await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: newName } });
|
||||
await updatePerson({ id: personToBeMergedInto.id, personUpdateDto: { name: newName } });
|
||||
|
||||
for (const person of people) {
|
||||
if (person.id === personToBeMergedIn.id) {
|
||||
if (person.id === personToBeMergedInto.id) {
|
||||
person.name = newName;
|
||||
break;
|
||||
}
|
||||
@@ -263,7 +255,7 @@
|
||||
|
||||
const onNameChangeSubmit = async (name: string, targetPerson: PersonResponseDto) => {
|
||||
try {
|
||||
if (name == targetPerson.name || showMergeModal) {
|
||||
if (name == targetPerson.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,7 +277,7 @@
|
||||
!person.isHidden,
|
||||
)
|
||||
.slice(0, 3);
|
||||
showMergeModal = true;
|
||||
await handleMerge();
|
||||
return;
|
||||
}
|
||||
await updateName(targetPerson.id, name);
|
||||
@@ -315,32 +307,10 @@
|
||||
(person) => person.name.toLowerCase() === name.toLowerCase() && person.id !== personId && person.name,
|
||||
);
|
||||
};
|
||||
|
||||
const handleMergeCancel = async () => {
|
||||
if (!personMerge1) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateName(personMerge1.id, newName);
|
||||
showMergeModal = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerHeight />
|
||||
|
||||
{#if showMergeModal && personMerge1 && personMerge2}
|
||||
<MergeSuggestionModal
|
||||
{personMerge1}
|
||||
{personMerge2}
|
||||
{potentialMergePeople}
|
||||
onClose={() => {
|
||||
showMergeModal = false;
|
||||
}}
|
||||
onReject={() => handleMergeCancel()}
|
||||
onConfirm={handleMergeSamePerson}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<UserPageLayout
|
||||
title={$t('people')}
|
||||
description={countVisiblePeople === 0 && !searchName ? undefined : `(${countVisiblePeople.toLocaleString($locale)})`}
|
||||
@@ -403,17 +373,16 @@
|
||||
onToggleFavorite={() => handleToggleFavorite(person)}
|
||||
/>
|
||||
|
||||
<form onsubmit={() => onNameChangeSubmit(newName, person)}>
|
||||
<input
|
||||
type="text"
|
||||
class=" bg-white dark:bg-immich-dark-gray border-gray-100 placeholder-gray-400 text-center dark:border-gray-900 w-full rounded-2xl mt-2 py-2 text-sm text-immich-primary dark:text-immich-dark-primary"
|
||||
value={person.name}
|
||||
placeholder={$t('add_a_name')}
|
||||
onfocusin={() => onNameChangeInputFocus(person)}
|
||||
onfocusout={() => onNameChangeSubmit(newName, person)}
|
||||
oninput={(event) => onNameChangeInputUpdate(event)}
|
||||
/>
|
||||
</form>
|
||||
<input
|
||||
type="text"
|
||||
class=" bg-white dark:bg-immich-dark-gray border-gray-100 placeholder-gray-400 text-center dark:border-gray-900 w-full rounded-2xl mt-2 py-2 text-sm text-immich-primary dark:text-immich-dark-primary"
|
||||
value={person.name}
|
||||
placeholder={$t('add_a_name')}
|
||||
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: (e) => e.currentTarget.blur() }}
|
||||
onfocusin={() => onNameChangeInputFocus(person)}
|
||||
onfocusout={() => onNameChangeSubmit(newName, person)}
|
||||
oninput={(event) => onNameChangeInputUpdate(event)}
|
||||
/>
|
||||
</div>
|
||||
{/snippet}
|
||||
</PeopleInfiniteScroll>
|
||||
|
||||
+30
-43
@@ -7,7 +7,6 @@
|
||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
|
||||
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
|
||||
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
||||
import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte';
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
||||
@@ -32,6 +31,7 @@
|
||||
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
|
||||
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { AssetStore } from '$lib/stores/assets-store.svelte';
|
||||
@@ -44,7 +44,6 @@
|
||||
import {
|
||||
AssetVisibility,
|
||||
getPersonStatistics,
|
||||
mergePerson,
|
||||
searchPerson,
|
||||
updatePerson,
|
||||
type AssetResponseDto,
|
||||
@@ -122,7 +121,7 @@
|
||||
});
|
||||
|
||||
const handleEscape = async () => {
|
||||
if ($showAssetViewer || viewMode === PersonPageViewMode.SUGGEST_MERGE) {
|
||||
if ($showAssetViewer) {
|
||||
return;
|
||||
}
|
||||
if (assetInteraction.selectionActive) {
|
||||
@@ -220,31 +219,32 @@
|
||||
viewMode = PersonPageViewMode.VIEW_ASSETS;
|
||||
};
|
||||
|
||||
const handleMergeSamePerson = async (response: [PersonResponseDto, PersonResponseDto]) => {
|
||||
const [personToMerge, personToBeMergedIn] = response;
|
||||
viewMode = PersonPageViewMode.VIEW_ASSETS;
|
||||
isEditingName = false;
|
||||
try {
|
||||
await mergePerson({
|
||||
id: personToBeMergedIn.id,
|
||||
mergePersonDto: { ids: [personToMerge.id] },
|
||||
});
|
||||
notificationController.show({
|
||||
message: $t('merge_people_successfully'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||
if (personToBeMergedIn.name != personName && person.id === personToBeMergedIn.id) {
|
||||
await updateAssetCount();
|
||||
return;
|
||||
}
|
||||
await goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true });
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_name'));
|
||||
const handleMergeSuggestion = async () => {
|
||||
if (!personMerge1 || !personMerge2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await modalManager.show(PersonMergeSuggestionModal, {
|
||||
personToMerge: personMerge1,
|
||||
personToBeMergedInto: personMerge2,
|
||||
potentialMergePeople,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [personToMerge, personToBeMergedInto] = result;
|
||||
|
||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||
if (personToBeMergedInto.name != personName && person.id === personToBeMergedInto.id) {
|
||||
await updateAssetCount();
|
||||
return;
|
||||
}
|
||||
await goto(`${AppRoute.PEOPLE}/${personToBeMergedInto.id}`, { replaceState: true });
|
||||
};
|
||||
|
||||
const handleSuggestPeople = (person2: PersonResponseDto) => {
|
||||
const handleSuggestPeople = async (person2: PersonResponseDto) => {
|
||||
isEditingName = false;
|
||||
if (person.id !== person2.id) {
|
||||
potentialMergePeople = [];
|
||||
@@ -252,7 +252,8 @@
|
||||
personMerge1 = person;
|
||||
personMerge2 = person2;
|
||||
isSuggestionSelectedByUser = true;
|
||||
viewMode = PersonPageViewMode.SUGGEST_MERGE;
|
||||
|
||||
await handleMergeSuggestion();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,9 +281,6 @@
|
||||
};
|
||||
|
||||
const handleCancelEditName = () => {
|
||||
if (viewMode === PersonPageViewMode.SUGGEST_MERGE) {
|
||||
return;
|
||||
}
|
||||
isSearchingPeople = false;
|
||||
isEditingName = false;
|
||||
};
|
||||
@@ -317,7 +315,7 @@
|
||||
!person.isHidden,
|
||||
)
|
||||
.slice(0, 3);
|
||||
viewMode = PersonPageViewMode.SUGGEST_MERGE;
|
||||
await handleMergeSuggestion();
|
||||
return;
|
||||
}
|
||||
await changeName();
|
||||
@@ -382,7 +380,7 @@
|
||||
onSelect={handleSelectFeaturePhoto}
|
||||
onEscape={handleEscape}
|
||||
>
|
||||
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE}
|
||||
{#if viewMode === PersonPageViewMode.VIEW_ASSETS}
|
||||
<!-- Person information block -->
|
||||
<div
|
||||
class="relative w-fit p-4 sm:px-6"
|
||||
@@ -497,17 +495,6 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if viewMode === PersonPageViewMode.SUGGEST_MERGE && personMerge1 && personMerge2}
|
||||
<MergeSuggestionModal
|
||||
{personMerge1}
|
||||
{personMerge2}
|
||||
{potentialMergePeople}
|
||||
onClose={() => (viewMode = PersonPageViewMode.VIEW_ASSETS)}
|
||||
onReject={changeName}
|
||||
onConfirm={handleMergeSamePerson}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if viewMode === PersonPageViewMode.MERGE_PEOPLE}
|
||||
<MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} />
|
||||
{/if}
|
||||
@@ -553,7 +540,7 @@
|
||||
</ButtonContextMenu>
|
||||
</AssetSelectControlBar>
|
||||
{:else}
|
||||
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE}
|
||||
{#if viewMode === PersonPageViewMode.VIEW_ASSETS}
|
||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(previousRoute)}>
|
||||
{#snippet trailing()}
|
||||
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
|
||||
Reference in New Issue
Block a user