diff --git a/i18n/en.json b/i18n/en.json index 5b14cd1803..e1538db1e4 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1196,6 +1196,7 @@ "sort_items": "Number of items", "sort_modified": "Date modified", "sort_oldest": "Oldest photo", + "sort_people_by_similarity": "Sort people by similarity", "sort_recent": "Most recent photo", "sort_title": "Title", "source": "Source", diff --git a/web/src/lib/components/elements/buttons/circle-icon-button.svelte b/web/src/lib/components/elements/buttons/circle-icon-button.svelte index 155062c1e2..071a645000 100644 --- a/web/src/lib/components/elements/buttons/circle-icon-button.svelte +++ b/web/src/lib/components/elements/buttons/circle-icon-button.svelte @@ -1,5 +1,5 @@ <script lang="ts" module> - export type Color = 'transparent' | 'light' | 'dark' | 'red' | 'gray' | 'primary' | 'opaque' | 'alert'; + export type Color = 'transparent' | 'light' | 'dark' | 'red' | 'gray' | 'primary' | 'opaque' | 'alert' | 'neutral'; export type Padding = '1' | '2' | '3'; </script> @@ -68,6 +68,8 @@ dark: 'bg-[#202123] hover:bg-[#d3d3d3]', alert: 'text-[#ff0000] hover:text-white', gray: 'bg-[#d3d3d3] hover:bg-[#e2e7e9] text-immich-dark-gray hover:text-black', + neutral: + 'dark:bg-immich-dark-gray dark:text-gray-300 hover:dark:bg-immich-dark-gray/50 hover:dark:text-gray-300 bg-gray-200 text-gray-700 hover:bg-gray-300', primary: 'bg-immich-primary dark:bg-immich-dark-primary hover:bg-immich-primary/75 hover:dark:bg-immich-dark-primary/80 text-white dark:text-immich-dark-gray', }; diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte index 906e67f220..0e68ebcfcb 100644 --- a/web/src/lib/components/faces-page/merge-face-selector.svelte +++ b/web/src/lib/components/faces-page/merge-face-selector.svelte @@ -34,10 +34,12 @@ let hasSelection = $derived(selectedPeople.length > 0); let peopleToNotShow = $derived([...selectedPeople, person]); - onMount(async () => { - const data = await getAllPeople({ withHidden: false, closestPersonId: person.id }); + const handleSearch = async (sortFaces: boolean = false) => { + const data = await getAllPeople({ withHidden: false, closestPersonId: sortFaces ? person.id : undefined }); people = data.people; - }); + }; + + onMount(handleSearch); const handleSwapPeople = async () => { [person, selectedPeople[0]] = [selectedPeople[0], person]; @@ -149,8 +151,7 @@ <FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} /> </div> </div> - - <PeopleList {people} {peopleToNotShow} {screenHeight} {onSelect} /> + <PeopleList {people} {peopleToNotShow} {screenHeight} {onSelect} {handleSearch} /> </section> </section> </section> diff --git a/web/src/lib/components/faces-page/people-list.svelte b/web/src/lib/components/faces-page/people-list.svelte index 511792e536..1c1eee39ec 100644 --- a/web/src/lib/components/faces-page/people-list.svelte +++ b/web/src/lib/components/faces-page/people-list.svelte @@ -3,18 +3,20 @@ import FaceThumbnail from './face-thumbnail.svelte'; import SearchPeople from '$lib/components/faces-page/people-search.svelte'; import { t } from 'svelte-i18n'; + import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; + import { mdiSwapVertical } from '@mdi/js'; interface Props { screenHeight: number; people: PersonResponseDto[]; peopleToNotShow: PersonResponseDto[]; onSelect: (person: PersonResponseDto) => void; + handleSearch?: (sortFaces: boolean) => void; } - let { screenHeight, people, peopleToNotShow, onSelect }: Props = $props(); - + let { screenHeight, people, peopleToNotShow, onSelect, handleSearch }: Props = $props(); let searchedPeopleLocal: PersonResponseDto[] = $state([]); - + let sortBySimilarirty = $state(false); let name = $state(''); const showPeople = $derived( @@ -24,12 +26,26 @@ ); </script> -<div class=" w-40 sm:w-48 md:w-96 h-14 mb-8"> - <SearchPeople type="searchBar" placeholder={$t('search_people')} bind:searchName={name} bind:searchedPeopleLocal /> +<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} + <CircleIconButton + icon={mdiSwapVertical} + onclick={() => { + sortBySimilarirty = !sortBySimilarirty; + handleSearch(sortBySimilarirty); + }} + color="neutral" + title={$t('sort_people_by_similarity')} + ></CircleIconButton> + {/if} </div> <div - class="immich-scrollbar overflow-y-auto rounded-3xl bg-gray-200 p-10 dark:bg-immich-dark-gray" + class="immich-scrollbar overflow-y-auto rounded-3xl bg-gray-200 p-10 dark:bg-immich-dark-gray mt-6" style:max-height={screenHeight - 400 + 'px'} > <div class="grid-col-2 grid gap-8 md:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10">