mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	feat(web): navigate with keyboard on person page (#5486)
* feat: navigate with keyboard on person page * pr feedback * pr feedback * pr feedback * fix: remove unused import
This commit is contained in:
		
							parent
							
								
									b4579e788b
								
							
						
					
					
						commit
						dcfd1f9ea6
					
				@ -27,6 +27,7 @@
 | 
			
		||||
  export let fullwidth = false;
 | 
			
		||||
  export let border = false;
 | 
			
		||||
  export let title: string | undefined = '';
 | 
			
		||||
 | 
			
		||||
  let className = '';
 | 
			
		||||
  export { className as class };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,6 @@
 | 
			
		||||
  import { mdiArrowLeft, mdiClose, mdiMerge } from '@mdi/js';
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher<{
 | 
			
		||||
    reject: void;
 | 
			
		||||
    confirm: [PersonResponseDto, PersonResponseDto];
 | 
			
		||||
    close: void;
 | 
			
		||||
  }>();
 | 
			
		||||
 | 
			
		||||
  export let personMerge1: PersonResponseDto;
 | 
			
		||||
  export let personMerge2: PersonResponseDto;
 | 
			
		||||
  export let potentialMergePeople: PersonResponseDto[];
 | 
			
		||||
@ -22,6 +16,21 @@
 | 
			
		||||
 | 
			
		||||
  const title = personMerge2.name;
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher<{
 | 
			
		||||
    reject: void;
 | 
			
		||||
    confirm: [PersonResponseDto, PersonResponseDto];
 | 
			
		||||
    close: void;
 | 
			
		||||
  }>();
 | 
			
		||||
 | 
			
		||||
  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
			
		||||
    switch (event.key) {
 | 
			
		||||
      case 'Escape': {
 | 
			
		||||
        dispatch('close');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const changePersonToMerge = (newperson: PersonResponseDto) => {
 | 
			
		||||
    const index = potentialMergePeople.indexOf(newperson);
 | 
			
		||||
    [potentialMergePeople[index], personMerge2] = [personMerge2, potentialMergePeople[index]];
 | 
			
		||||
@ -29,6 +38,8 @@
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:document on:keypress={handleKeyboardPress} />
 | 
			
		||||
 | 
			
		||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
 | 
			
		||||
  <div class="flex h-full w-full place-content-center place-items-center overflow-hidden">
 | 
			
		||||
    <div
 | 
			
		||||
@ -114,7 +125,7 @@
 | 
			
		||||
        <p class="text-sm text-gray-500 dark:text-gray-300">They will be merged together</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="mt-8 flex w-full gap-4 px-4 pb-4">
 | 
			
		||||
        <Button color="gray" fullwidth on:click={() => dispatch('reject')}>No</Button>
 | 
			
		||||
        <Button fullwidth color="gray" on:click={() => dispatch('reject')}>No</Button>
 | 
			
		||||
        <Button fullwidth on:click={() => dispatch('confirm', [personMerge1, personMerge2])}>Yes</Button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -88,6 +88,8 @@
 | 
			
		||||
   **/
 | 
			
		||||
  let searchWord: string;
 | 
			
		||||
  let isSearchingPeople = false;
 | 
			
		||||
  let focusedElements: (HTMLButtonElement | null)[] = Array.from({ length: maximumLengthSearchPeople }, () => null);
 | 
			
		||||
  let indexFocus: number | null = null;
 | 
			
		||||
 | 
			
		||||
  const searchPeople = async () => {
 | 
			
		||||
    if ((people.length < maximumLengthSearchPeople && name.startsWith(searchWord)) || name === '') {
 | 
			
		||||
@ -116,6 +118,7 @@
 | 
			
		||||
  $: {
 | 
			
		||||
    if (people) {
 | 
			
		||||
      suggestedPeople = name ? searchNameLocal(name, people, 5, data.person.id) : [];
 | 
			
		||||
      indexFocus = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -129,8 +132,51 @@
 | 
			
		||||
      viewMode = ViewMode.MERGE_PEOPLE;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
			
		||||
    if (suggestedPeople.length === 0) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!$showAssetViewer) {
 | 
			
		||||
      event.stopPropagation();
 | 
			
		||||
      switch (event.key) {
 | 
			
		||||
        case 'ArrowDown': {
 | 
			
		||||
          event.preventDefault();
 | 
			
		||||
          if (indexFocus === null) {
 | 
			
		||||
            indexFocus = 0;
 | 
			
		||||
          } else if (indexFocus === suggestedPeople.length - 1) {
 | 
			
		||||
            indexFocus = 0;
 | 
			
		||||
          } else {
 | 
			
		||||
            indexFocus++;
 | 
			
		||||
          }
 | 
			
		||||
          focusedElements[indexFocus]?.focus();
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'ArrowUp': {
 | 
			
		||||
          if (indexFocus === null) {
 | 
			
		||||
            indexFocus = 0;
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          if (indexFocus === 0) {
 | 
			
		||||
            indexFocus = suggestedPeople.length - 1;
 | 
			
		||||
          } else {
 | 
			
		||||
            indexFocus--;
 | 
			
		||||
          }
 | 
			
		||||
          focusedElements[indexFocus]?.focus();
 | 
			
		||||
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        case 'Enter': {
 | 
			
		||||
          if (indexFocus !== null) {
 | 
			
		||||
            handleSuggestPeople(suggestedPeople[indexFocus]);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleEscape = () => {
 | 
			
		||||
    if ($showAssetViewer) {
 | 
			
		||||
    if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($isMultiSelectState) {
 | 
			
		||||
@ -350,6 +396,7 @@
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:document on:keydown={handleKeyboardPress} />
 | 
			
		||||
{#if viewMode === ViewMode.UNASSIGN_ASSETS}
 | 
			
		||||
  <UnMergeFaceSelector
 | 
			
		||||
    assetIds={[...$selectedAssets].map((a) => a.id)}
 | 
			
		||||
@ -499,13 +546,14 @@
 | 
			
		||||
                </div>
 | 
			
		||||
              {:else}
 | 
			
		||||
                {#each suggestedPeople as person, index (person.id)}
 | 
			
		||||
                  <div
 | 
			
		||||
                    class="flex border-t border-x border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] {index ===
 | 
			
		||||
                  <button
 | 
			
		||||
                    bind:this={focusedElements[index]}
 | 
			
		||||
                    class="flex w-full border-t border-gray-400 dark:border-immich-dark-gray h-14 place-items-center bg-gray-200 p-2 dark:bg-gray-700 hover:bg-gray-300 hover:dark:bg-[#232932] focus:bg-gray-300 focus:dark:bg-[#232932] {index ===
 | 
			
		||||
                    suggestedPeople.length - 1
 | 
			
		||||
                      ? 'rounded-b-lg border-b'
 | 
			
		||||
                      : ''}"
 | 
			
		||||
                    on:click={() => handleSuggestPeople(person)}
 | 
			
		||||
                  >
 | 
			
		||||
                    <button class="flex w-full place-items-center" on:click={() => handleSuggestPeople(person)}>
 | 
			
		||||
                    <ImageThumbnail
 | 
			
		||||
                      circle
 | 
			
		||||
                      shadow
 | 
			
		||||
@ -516,7 +564,6 @@
 | 
			
		||||
                    />
 | 
			
		||||
                    <p class="ml-4 text-gray-700 dark:text-gray-100">{person.name}</p>
 | 
			
		||||
                  </button>
 | 
			
		||||
                  </div>
 | 
			
		||||
                {/each}
 | 
			
		||||
              {/if}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user