bmondream 2025-12-10 17:15:12 +07:00 committed by GitHub
commit 81f2af8fd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 65 additions and 15 deletions

@ -721,6 +721,7 @@
"check_logs": "Check Logs",
"checksum": "Checksum",
"choose_matching_people_to_merge": "Choose matching people to merge",
"choose_people_to_unselect": "Choose people to unselect",
"city": "City",
"clear": "Clear",
"clear_all": "Clear all",
@ -1401,7 +1402,6 @@
"menu": "Menu",
"merge": "Merge",
"merge_people": "Merge people",
"merge_people_limit": "You can only merge up to 5 faces at a time",
"merge_people_prompt": "Do you want to merge these people? This action is irreversible.",
"merge_people_successfully": "Merge people successfully",
"merged_people_count": "Merged {count, plural, one {# person} other {# people}}",
@ -1854,6 +1854,7 @@
"selected": "Selected",
"selected_count": "{count, plural, other {# selected}}",
"selected_gps_coordinates": "Selected GPS Coordinates",
"selected_people_to_merge": "Selected people to merge",
"send_message": "Send message",
"send_welcome_email": "Send welcome email",
"server_endpoint": "Server Endpoint",
@ -1970,6 +1971,7 @@
"show_album_options": "Show album options",
"show_albums": "Show albums",
"show_all_people": "Show all people",
"show_all_selected_people": "Show all selected people",
"show_and_hide_people": "Show & hide people",
"show_file_location": "Show file location",
"show_gallery": "Show gallery",

@ -5,7 +5,7 @@
import { handleError } from '$lib/utils/handle-error';
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
import { mdiCallMerge, mdiMerge, mdiPlus, mdiSwapHorizontal } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { flip } from 'svelte/animate';
@ -14,6 +14,7 @@
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import FaceThumbnail from './face-thumbnail.svelte';
import PeopleList from './people-list.svelte';
import PeopleViewModal from "$lib/modals/PeopleViewModal.svelte";
interface Props {
person: PersonResponseDto;
@ -49,11 +50,6 @@
return;
}
if (selectedPeople.length >= 5) {
toastManager.warning($t('merge_people_limit'));
return;
}
selectedPeople = [selected, ...selectedPeople];
if (selectedPeople.length === 1 && !person.name && selected.name) {
@ -80,6 +76,15 @@
handleError(error, $t('cannot_merge_people'));
}
};
const showPeopleViewModal = async () => {
// Sets the selected people that remained after closing the modal.
selectedPeople = await modalManager.show(PeopleViewModal, {
people: selectedPeople,
peopleToNotShow: [person],
screenHeight
});
};
</script>
<svelte:window bind:innerHeight={screenHeight} />
@ -109,7 +114,7 @@
<p class="mb-4 text-center uppercase dark:text-white">{$t('choose_matching_people_to_merge')}</p>
<div class="grid grid-flow-col-dense place-content-center place-items-center gap-4">
{#each selectedPeople as person (person.id)}
{#each selectedPeople.slice(0, 5) as person, _ (person.id)}
<div animate:flip={{ duration: 250, easing: quintOut }}>
<FaceThumbnail border circle {person} selectable thumbnailSize={120} onClick={() => onSelect(person)} />
</div>
@ -119,6 +124,18 @@
<div class="relative h-full">
<div class="flex flex-col h-full justify-between">
<div class="flex h-full items-center justify-center">
{#if selectedPeople.length > 5}
<div class="absolute top-2">
<IconButton
shape="round"
color="secondary"
aria-label={$t('show_all_selected_people')}
icon={mdiPlus}
size="medium"
onclick={showPeopleViewModal}
/>
</div>
{/if}
<Icon icon={mdiCallMerge} size="48" class="rotate-90 dark:text-white" />
</div>
{#if selectedPeople.length === 1}

@ -16,7 +16,7 @@
let { screenHeight, people, peopleToNotShow, onSelect, handleSearch }: Props = $props();
let searchedPeopleLocal: PersonResponseDto[] = $state([]);
let sortBySimilarirty = $state(false);
let sortBySimilarity = $state(false);
let name = $state('');
const showPeople = $derived(
@ -27,19 +27,19 @@
</script>
<div class="w-40 sm:w-48 md:w-full h-14 flex gap-4 place-items-center">
<div class="md:w-96">
<SearchPeople type="searchBar" placeholder={$t('search_people')} bind:searchName={name} bind:searchedPeopleLocal />
</div>
{#if handleSearch}
<div class="md:w-96">
<SearchPeople type="searchBar" placeholder={$t('search_people')} bind:searchName={name} bind:searchedPeopleLocal />
</div>
<IconButton
shape="round"
color="secondary"
variant="ghost"
icon={mdiSwapVertical}
onclick={() => {
sortBySimilarirty = !sortBySimilarirty;
handleSearch(sortBySimilarirty);
sortBySimilarity = !sortBySimilarity;
handleSearch(sortBySimilarity);
}}
aria-label={$t('sort_people_by_similarity')}
/>

@ -0,0 +1,31 @@
<script lang="ts">
import { Modal, ModalBody } from "@immich/ui";
import PeopleList from "$lib/components/faces-page/people-list.svelte";
import { t } from 'svelte-i18n';
import type { PersonResponseDto } from '@immich/sdk';
interface Props {
people: PersonResponseDto[];
peopleToNotShow: PersonResponseDto[];
screenHeight: number;
onClose: (people: PersonResponseDto[]) => void;
}
let { people, peopleToNotShow, screenHeight, onClose }: Props = $props();
// Hides the selected person.
const onSelect = (selected: PersonResponseDto) => {
if (people.includes(selected)) {
people = people.filter((person) => person.id !== selected.id);
return;
}
};
</script>
<Modal title={$t('selected_people_to_merge')} size="full" onClose={() => onClose(people)}>
<ModalBody>
<p class="mb-4 text-center dark:text-white">{$t('choose_people_to_unselect')}</p>
<PeopleList {people} {peopleToNotShow} {screenHeight} {onSelect} />
</ModalBody>
</Modal>