fix: people page search (#32108)

fix: people page search (#32094)

(cherry picked from commit 549a76cd35ec9f63a76174992de50095192afa11)

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
This commit is contained in:
immich-push-o-matic[bot]
2026-10-05 16:23:02 +00:00
committed by GitHub
co-authored by Daniel Dietzler
parent 983177d570
commit 81de979a35
8 changed files with 89 additions and 64 deletions
+1
View File
@@ -81,6 +81,7 @@ export type PeopleFilter = {
sharedWithId?: string;
isFavorite?: boolean;
isHidden?: boolean;
name?: string;
};
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object';
+41 -59
View File
@@ -1,26 +1,23 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { page } from '$app/state';
import { scrollMemory } from '$lib/actions/scroll-memory';
import { shortcut } from '$lib/actions/shortcut';
import PeopleCard from './PeopleCard.svelte';
import PeopleInfiniteScroll from './PeopleInfiniteScroll.svelte';
import SearchPeople from '$lib/components/faces-page/PeopleSearch.svelte';
import UserPageLayout from '$lib/components/layouts/UserPageLayout.svelte';
import OnEvents from '$lib/components/OnEvents.svelte';
import { QueryParameter, SessionStorageKey } from '$lib/constants';
import SearchBar from '$lib/elements/SearchBar.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import PeopleFilterModal from '$lib/modals/PeopleFilterModal.svelte';
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
import { Route } from '$lib/route';
import { locale } from '$lib/stores/preferences.store';
import { websocketEvents } from '$lib/stores/websocket';
import { normalizeSearchString } from '$lib/utils/string-utils';
import { handlePromiseError } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { clearQueryParam } from '$lib/utils/navigation';
import { getPeopleUserActions } from '$lib/services/person-user.service';
import { handleUpdatePersonName } from '$lib/services/person.service';
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 { normalizeSearchString } from '$lib/utils/string-utils';
import {
getAllPeople,
getClusterGroupUsers,
@@ -31,9 +28,11 @@
} from '@immich/sdk';
import { ActionButton, Button, Icon, IconButton, modalManager } from '@immich/ui';
import { mdiAccountOff, mdiEyeOutline, mdiTune } from '@mdi/js';
import { onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
import PeopleCard from './PeopleCard.svelte';
import PeopleInfiniteScroll from './PeopleInfiniteScroll.svelte';
interface Props {
data: PageData;
@@ -49,9 +48,7 @@
let personMerge2 = $state<PersonResponseDto>();
let potentialMergePeople: PersonResponseDto[] = $state([]);
let editingPerson: PersonResponseDto | null = $state(null);
let searchedPeopleLocal: PersonResponseDto[] = $state([]);
let innerHeight = $state(0);
let searchPeopleElement = $state<ReturnType<typeof SearchPeople>>();
let clusterGroupUsers: UserResponseDto[] = $state([]);
@@ -64,16 +61,8 @@
}
});
onMount(() => {
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
if (getSearchedPeople) {
searchName = getSearchedPeople;
if (searchPeopleElement) {
handlePromiseError(searchPeopleElement.searchPeople(true, searchName));
}
}
return websocketEvents.on('on_person_thumbnail', (personId: string) => {
onDestroy(() => {
websocketEvents.on('on_person_thumbnail', (personId: string) => {
for (const person of people) {
if (person.id === personId) {
person.updatedAt = new Date().toISOString();
@@ -133,11 +122,15 @@
}
};
const handleSearch = async () => {
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
const handleSearchByName = async () => {
const getSearchedPeople = page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
if (getSearchedPeople !== searchName) {
$page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
await goto($page.url, { keepFocus: true });
if (searchName) {
page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
} else {
page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE);
}
await goto(page.url, { keepFocus: true, replaceState: true, invalidateAll: true });
}
};
@@ -187,7 +180,7 @@
return;
}
const url = new URL($page.url);
const url = new URL(page.url);
for (const [key, value] of [
[QueryParameter.SHARED_BY_ID, filter.sharedById],
[QueryParameter.SHARED_WITH_ID, filter.sharedWithId],
@@ -206,19 +199,12 @@
nextPage = data.people.hasNextPage ? 2 : null;
};
const onResetSearchBar = async () => {
await clearQueryParam(QueryParameter.SEARCHED_PEOPLE, $page.url);
};
let people = $derived(data.people.people);
const { ManageAccess } = $derived(getPeopleUserActions($t, clusterGroupUsers, nextPage ? undefined : people));
// hidden people are only shown when explicitly filtering for them
let visiblePeople = $derived(data.filter.isHidden ? people : people.filter((people) => !people.isHidden));
let countVisiblePeople = $derived(
searchName ? searchedPeopleLocal.length : data.people.total - (data.filter.isHidden ? 0 : data.people.hidden),
);
let showPeople = $derived(searchName ? searchedPeopleLocal : visiblePeople);
let countVisiblePeople = $derived(data.people.total - (data.filter.isHidden ? 0 : data.people.hidden));
const onNameChangeInputFocus = (person: PersonResponseDto) => {
editingPerson = person;
@@ -314,28 +300,24 @@
>
{#snippet buttons()}
<div class="flex items-center justify-center gap-2">
{#if people.length > 0}
<div class="hidden sm:block">
<div class="h-10 w-40 lg:w-80">
<SearchPeople
bind:this={searchPeopleElement}
type="searchBar"
placeholder={$t('search_people')}
onReset={onResetSearchBar}
onSearch={handleSearch}
bind:searchName
bind:searchedPeopleLocal
/>
</div>
<div class="hidden sm:block">
<div class="h-10 w-40 lg:w-80">
<SearchBar
bind:name={searchName}
placeholder={$t('search_people')}
onSearch={() => handleSearchByName()}
showLoadingSpinner={false}
onReset={() => handleSearchByName()}
/>
</div>
<Button
leadingIcon={mdiEyeOutline}
onclick={() => goto('/people/manage')}
size="small"
variant="ghost"
color="secondary">{$t('show_and_hide_people')}</Button
>
{/if}
</div>
<Button
leadingIcon={mdiEyeOutline}
onclick={() => goto('/people/manage')}
size="small"
variant="ghost"
color="secondary">{$t('show_and_hide_people')}</Button
>
<ActionButton action={ManageAccess} />
<IconButton
shape="round"
@@ -349,8 +331,8 @@
</div>
{/snippet}
{#if countVisiblePeople > 0 && (!searchName || searchedPeopleLocal.length > 0)}
<PeopleInfiniteScroll people={showPeople} hasNextPage={!!nextPage && !searchName} {loadNextPage}>
{#if countVisiblePeople > 0}
<PeopleInfiniteScroll people={visiblePeople} hasNextPage={!!nextPage && !searchName} {loadNextPage}>
{#snippet children({ person })}
<div
class="rounded-xl border-2 border-transparent p-2 transition-all hover:border-immich-primary/50 hover:bg-gray-200 hover:shadow-sm hover:dark:border-immich-dark-primary/25 dark:hover:bg-immich-dark-primary/20"
+1
View File
@@ -18,6 +18,7 @@ export const load = (async ({ url }) => {
sharedWithId: url.searchParams.get(QueryParameter.SHARED_WITH_ID) ?? undefined,
isFavorite: getBoolean(QueryParameter.IS_FAVORITE),
isHidden: getBoolean(QueryParameter.IS_HIDDEN),
name: url.searchParams.get(QueryParameter.SEARCHED_PEOPLE) ?? undefined,
};
const people = await getAllPeople({ withHidden: true, ...filter });
const $t = await getFormatter();