mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	feat(web): improve show & hide people accessibility (#10954)
This commit is contained in:
		
							parent
							
								
									eb89208abb
								
							
						
					
					
						commit
						a0f6d7444a
					
				@ -3,6 +3,7 @@ import ManagePeopleVisibility from '$lib/components/faces-page/manage-people-vis
 | 
				
			|||||||
import type { PersonResponseDto } from '@immich/sdk';
 | 
					import type { PersonResponseDto } from '@immich/sdk';
 | 
				
			||||||
import { personFactory } from '@test-data/factories/person-factory';
 | 
					import { personFactory } from '@test-data/factories/person-factory';
 | 
				
			||||||
import { render } from '@testing-library/svelte';
 | 
					import { render } from '@testing-library/svelte';
 | 
				
			||||||
 | 
					import { tick } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('ManagePeopleVisibility Component', () => {
 | 
					describe('ManagePeopleVisibility Component', () => {
 | 
				
			||||||
  let personVisible: PersonResponseDto;
 | 
					  let personVisible: PersonResponseDto;
 | 
				
			||||||
@ -48,10 +49,8 @@ describe('ManagePeopleVisibility Component', () => {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const toggleButton = getByTitle('toggle_visibility');
 | 
					    getByTitle('hide_unnamed_people').click();
 | 
				
			||||||
    toggleButton.click();
 | 
					    getByText('done').click();
 | 
				
			||||||
    const saveButton = getByText('done');
 | 
					 | 
				
			||||||
    saveButton.click();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
					    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
				
			||||||
      peopleUpdateDto: {
 | 
					      peopleUpdateDto: {
 | 
				
			||||||
@ -60,7 +59,7 @@ describe('ManagePeopleVisibility Component', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('hides all people on second button press', () => {
 | 
					  it('hides all people on second button press', async () => {
 | 
				
			||||||
    const { getByText, getByTitle } = render(ManagePeopleVisibility, {
 | 
					    const { getByText, getByTitle } = render(ManagePeopleVisibility, {
 | 
				
			||||||
      props: {
 | 
					      props: {
 | 
				
			||||||
        people: [personVisible, personHidden, personWithoutName],
 | 
					        people: [personVisible, personHidden, personWithoutName],
 | 
				
			||||||
@ -68,11 +67,10 @@ describe('ManagePeopleVisibility Component', () => {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const toggleButton = getByTitle('toggle_visibility');
 | 
					    getByTitle('hide_unnamed_people').click();
 | 
				
			||||||
    toggleButton.click();
 | 
					    await tick();
 | 
				
			||||||
    toggleButton.click();
 | 
					    getByTitle('hide_all_people').click();
 | 
				
			||||||
    const saveButton = getByText('done');
 | 
					    getByText('done').click();
 | 
				
			||||||
    saveButton.click();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
					    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
				
			||||||
      peopleUpdateDto: {
 | 
					      peopleUpdateDto: {
 | 
				
			||||||
@ -84,7 +82,7 @@ describe('ManagePeopleVisibility Component', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows all people on third button press', () => {
 | 
					  it('shows all people on third button press', async () => {
 | 
				
			||||||
    const { getByText, getByTitle } = render(ManagePeopleVisibility, {
 | 
					    const { getByText, getByTitle } = render(ManagePeopleVisibility, {
 | 
				
			||||||
      props: {
 | 
					      props: {
 | 
				
			||||||
        people: [personVisible, personHidden, personWithoutName],
 | 
					        people: [personVisible, personHidden, personWithoutName],
 | 
				
			||||||
@ -92,12 +90,12 @@ describe('ManagePeopleVisibility Component', () => {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const toggleButton = getByTitle('toggle_visibility');
 | 
					    getByTitle('hide_unnamed_people').click();
 | 
				
			||||||
    toggleButton.click();
 | 
					    await tick();
 | 
				
			||||||
    toggleButton.click();
 | 
					    getByTitle('hide_all_people').click();
 | 
				
			||||||
    toggleButton.click();
 | 
					    await tick();
 | 
				
			||||||
    const saveButton = getByText('done');
 | 
					    getByTitle('show_all_people').click();
 | 
				
			||||||
    saveButton.click();
 | 
					    getByText('done').click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
					    expect(sdkMock.updatePeople).toHaveBeenCalledWith({
 | 
				
			||||||
      peopleUpdateDto: {
 | 
					      peopleUpdateDto: {
 | 
				
			||||||
 | 
				
			|||||||
@ -25,12 +25,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  export let people: PersonResponseDto[];
 | 
					  export let people: PersonResponseDto[];
 | 
				
			||||||
  export let onClose: () => void;
 | 
					  export let onClose: () => void;
 | 
				
			||||||
 | 
					  export let titleId: string | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let toggleVisibility = ToggleVisibility.SHOW_ALL;
 | 
					  let toggleVisibility = ToggleVisibility.SHOW_ALL;
 | 
				
			||||||
  let showLoadingSpinner = false;
 | 
					  let showLoadingSpinner = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: personIsHidden = getPersonIsHidden(people);
 | 
					  $: personIsHidden = getPersonIsHidden(people);
 | 
				
			||||||
  $: toggleIcon = toggleIconOptions[toggleVisibility];
 | 
					  $: toggleButton = toggleButtonOptions[getNextVisibility(toggleVisibility)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getPersonIsHidden = (people: PersonResponseDto[]) => {
 | 
					  const getPersonIsHidden = (people: PersonResponseDto[]) => {
 | 
				
			||||||
    const personIsHidden: Record<string, boolean> = {};
 | 
					    const personIsHidden: Record<string, boolean> = {};
 | 
				
			||||||
@ -40,11 +41,13 @@
 | 
				
			|||||||
    return personIsHidden;
 | 
					    return personIsHidden;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleIconOptions: Record<ToggleVisibility, string> = {
 | 
					  $: toggleButtonOptions = ((): Record<ToggleVisibility, { icon: string; label: string }> => {
 | 
				
			||||||
    [ToggleVisibility.HIDE_ALL]: mdiEyeOff,
 | 
					    return {
 | 
				
			||||||
    [ToggleVisibility.HIDE_UNNANEMD]: mdiEyeSettings,
 | 
					      [ToggleVisibility.HIDE_ALL]: { icon: mdiEyeOff, label: $t('hide_all_people') },
 | 
				
			||||||
    [ToggleVisibility.SHOW_ALL]: mdiEye,
 | 
					      [ToggleVisibility.HIDE_UNNANEMD]: { icon: mdiEyeSettings, label: $t('hide_unnamed_people') },
 | 
				
			||||||
  };
 | 
					      [ToggleVisibility.SHOW_ALL]: { icon: mdiEye, label: $t('show_all_people') },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getNextVisibility = (toggleVisibility: ToggleVisibility) => {
 | 
					  const getNextVisibility = (toggleVisibility: ToggleVisibility) => {
 | 
				
			||||||
    if (toggleVisibility === ToggleVisibility.SHOW_ALL) {
 | 
					    if (toggleVisibility === ToggleVisibility.SHOW_ALL) {
 | 
				
			||||||
@ -117,14 +120,14 @@
 | 
				
			|||||||
  <div class="flex items-center">
 | 
					  <div class="flex items-center">
 | 
				
			||||||
    <CircleIconButton title={$t('close')} icon={mdiClose} on:click={onClose} />
 | 
					    <CircleIconButton title={$t('close')} icon={mdiClose} on:click={onClose} />
 | 
				
			||||||
    <div class="flex gap-2 items-center">
 | 
					    <div class="flex gap-2 items-center">
 | 
				
			||||||
      <p class="ml-2">{$t('show_and_hide_people')}</p>
 | 
					      <p id={titleId} class="ml-2">{$t('show_and_hide_people')}</p>
 | 
				
			||||||
      <p class="text-sm text-gray-400 dark:text-gray-600">({people.length.toLocaleString($locale)})</p>
 | 
					      <p class="text-sm text-gray-400 dark:text-gray-600">({people.length.toLocaleString($locale)})</p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div class="flex items-center justify-end">
 | 
					  <div class="flex items-center justify-end">
 | 
				
			||||||
    <div class="flex items-center md:mr-4">
 | 
					    <div class="flex items-center md:mr-4">
 | 
				
			||||||
      <CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} on:click={handleResetVisibility} />
 | 
					      <CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} on:click={handleResetVisibility} />
 | 
				
			||||||
      <CircleIconButton title={$t('toggle_visibility')} icon={toggleIcon} on:click={handleToggleVisibility} />
 | 
					      <CircleIconButton title={toggleButton.label} icon={toggleButton.icon} on:click={handleToggleVisibility} />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    {#if !showLoadingSpinner}
 | 
					    {#if !showLoadingSpinner}
 | 
				
			||||||
      <Button on:click={handleSaveVisibility} size="sm" rounded="lg">{$t('done')}</Button>
 | 
					      <Button on:click={handleSaveVisibility} size="sm" rounded="lg">{$t('done')}</Button>
 | 
				
			||||||
@ -137,14 +140,17 @@
 | 
				
			|||||||
<div class="flex flex-wrap gap-1 bg-immich-bg p-2 pb-8 dark:bg-immich-dark-bg md:px-8 mt-16">
 | 
					<div class="flex flex-wrap gap-1 bg-immich-bg p-2 pb-8 dark:bg-immich-dark-bg md:px-8 mt-16">
 | 
				
			||||||
  <div class="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-9 gap-1">
 | 
					  <div class="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-9 gap-1">
 | 
				
			||||||
    {#each people as person, index (person.id)}
 | 
					    {#each people as person, index (person.id)}
 | 
				
			||||||
 | 
					      {@const hidden = personIsHidden[person.id]}
 | 
				
			||||||
      <button
 | 
					      <button
 | 
				
			||||||
        type="button"
 | 
					        type="button"
 | 
				
			||||||
        class="group relative"
 | 
					        class="group relative"
 | 
				
			||||||
        on:click={() => (personIsHidden[person.id] = !personIsHidden[person.id])}
 | 
					        on:click={() => (personIsHidden[person.id] = !hidden)}
 | 
				
			||||||
 | 
					        aria-pressed={hidden}
 | 
				
			||||||
 | 
					        aria-label={person.name ? $t('hide_named_person', { values: { name: person.name } }) : $t('hide_person')}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <ImageThumbnail
 | 
					        <ImageThumbnail
 | 
				
			||||||
          preload={index < 20}
 | 
					          preload={index < 20}
 | 
				
			||||||
          hidden={personIsHidden[person.id]}
 | 
					          {hidden}
 | 
				
			||||||
          shadow
 | 
					          shadow
 | 
				
			||||||
          url={getPeopleThumbnailUrl(person)}
 | 
					          url={getPeopleThumbnailUrl(person)}
 | 
				
			||||||
          altText={person.name}
 | 
					          altText={person.name}
 | 
				
			||||||
 | 
				
			|||||||
@ -692,9 +692,12 @@
 | 
				
			|||||||
  "group_year": "Group by year",
 | 
					  "group_year": "Group by year",
 | 
				
			||||||
  "has_quota": "Has quota",
 | 
					  "has_quota": "Has quota",
 | 
				
			||||||
  "hi_user": "Hi {name} ({email})",
 | 
					  "hi_user": "Hi {name} ({email})",
 | 
				
			||||||
 | 
					  "hide_all_people": "Hide all people",
 | 
				
			||||||
  "hide_gallery": "Hide gallery",
 | 
					  "hide_gallery": "Hide gallery",
 | 
				
			||||||
 | 
					  "hide_named_person": "Hide person {name}",
 | 
				
			||||||
  "hide_password": "Hide password",
 | 
					  "hide_password": "Hide password",
 | 
				
			||||||
  "hide_person": "Hide person",
 | 
					  "hide_person": "Hide person",
 | 
				
			||||||
 | 
					  "hide_unnamed_people": "Hide unnamed people",
 | 
				
			||||||
  "host": "Host",
 | 
					  "host": "Host",
 | 
				
			||||||
  "hour": "Hour",
 | 
					  "hour": "Hour",
 | 
				
			||||||
  "image": "Image",
 | 
					  "image": "Image",
 | 
				
			||||||
@ -1022,6 +1025,7 @@
 | 
				
			|||||||
  "sharing_sidebar_description": "Display a link to Sharing in the sidebar",
 | 
					  "sharing_sidebar_description": "Display a link to Sharing in the sidebar",
 | 
				
			||||||
  "shift_to_permanent_delete": "press ⇧ to permanently delete asset",
 | 
					  "shift_to_permanent_delete": "press ⇧ to permanently delete asset",
 | 
				
			||||||
  "show_album_options": "Show album options",
 | 
					  "show_album_options": "Show album options",
 | 
				
			||||||
 | 
					  "show_all_people": "Show all people",
 | 
				
			||||||
  "show_and_hide_people": "Show & hide people",
 | 
					  "show_and_hide_people": "Show & hide people",
 | 
				
			||||||
  "show_file_location": "Show file location",
 | 
					  "show_file_location": "Show file location",
 | 
				
			||||||
  "show_gallery": "Show gallery",
 | 
					  "show_gallery": "Show gallery",
 | 
				
			||||||
@ -1084,7 +1088,6 @@
 | 
				
			|||||||
  "to_trash": "Trash",
 | 
					  "to_trash": "Trash",
 | 
				
			||||||
  "toggle_settings": "Toggle settings",
 | 
					  "toggle_settings": "Toggle settings",
 | 
				
			||||||
  "toggle_theme": "Toggle theme",
 | 
					  "toggle_theme": "Toggle theme",
 | 
				
			||||||
  "toggle_visibility": "Toggle visibility",
 | 
					 | 
				
			||||||
  "total_usage": "Total usage",
 | 
					  "total_usage": "Total usage",
 | 
				
			||||||
  "trash": "Trash",
 | 
					  "trash": "Trash",
 | 
				
			||||||
  "trash_all": "Trash All",
 | 
					  "trash_all": "Trash All",
 | 
				
			||||||
 | 
				
			|||||||
@ -28,6 +28,7 @@
 | 
				
			|||||||
  import { quintOut } from 'svelte/easing';
 | 
					  import { quintOut } from 'svelte/easing';
 | 
				
			||||||
  import { fly } from 'svelte/transition';
 | 
					  import { fly } from 'svelte/transition';
 | 
				
			||||||
  import type { PageData } from './$types';
 | 
					  import type { PageData } from './$types';
 | 
				
			||||||
 | 
					  import { focusTrap } from '$lib/actions/focus-trap';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let data: PageData;
 | 
					  export let data: PageData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -376,11 +377,14 @@
 | 
				
			|||||||
</UserPageLayout>
 | 
					</UserPageLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if selectHidden}
 | 
					{#if selectHidden}
 | 
				
			||||||
  <!-- TODO: Add focus trap -->
 | 
					 | 
				
			||||||
  <section
 | 
					  <section
 | 
				
			||||||
    transition:fly={{ y: innerHeight, duration: 150, easing: quintOut, opacity: 0 }}
 | 
					    transition:fly={{ y: innerHeight, duration: 150, easing: quintOut, opacity: 0 }}
 | 
				
			||||||
    class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
 | 
					    class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
 | 
				
			||||||
 | 
					    role="dialog"
 | 
				
			||||||
 | 
					    aria-modal="true"
 | 
				
			||||||
 | 
					    aria-labelledby="manage-visibility-title"
 | 
				
			||||||
 | 
					    use:focusTrap
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <ManagePeopleVisibility bind:people onClose={() => (selectHidden = false)} />
 | 
					    <ManagePeopleVisibility bind:people titleId="manage-visibility-title" onClose={() => (selectHidden = false)} />
 | 
				
			||||||
  </section>
 | 
					  </section>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user