mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	feat(web): Duplicate-Page shortcut changes (#11183)
* duplicate page assign other shortcut keys, add 'open image' shortcut * add shortcut info page to duplicates with own list of keys * edit translations, add translationkeys * format fix * remove typo --------- Co-authored-by: Zack Pollard <zackpollard@ymail.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									a78eeb9b9c
								
							
						
					
					
						commit
						e1ac73718c
					
				@ -16,7 +16,7 @@
 | 
				
			|||||||
    info?: string;
 | 
					    info?: string;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const shortcuts: Shortcuts = {
 | 
					  export let shortcuts: Shortcuts = {
 | 
				
			||||||
    general: [
 | 
					    general: [
 | 
				
			||||||
      { key: ['←', '→'], action: $t('previous_or_next_photo') },
 | 
					      { key: ['←', '→'], action: $t('previous_or_next_photo') },
 | 
				
			||||||
      { key: ['Esc'], action: $t('back_close_deselect') },
 | 
					      { key: ['Esc'], action: $t('back_close_deselect') },
 | 
				
			||||||
@ -40,45 +40,48 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" onClose={() => dispatch('close')}>
 | 
					<FullScreenModal title={$t('keyboard_shortcuts')} width="auto" onClose={() => dispatch('close')}>
 | 
				
			||||||
  <div class="grid grid-cols-1 gap-4 px-4 pb-4 md:grid-cols-2">
 | 
					  <div class="grid grid-cols-1 gap-4 px-4 pb-4 md:grid-cols-2">
 | 
				
			||||||
    <div class="p-4">
 | 
					    {#if shortcuts.general.length > 0}
 | 
				
			||||||
      <h2>{$t('general')}</h2>
 | 
					      <div class="p-4">
 | 
				
			||||||
      <div class="text-sm">
 | 
					        <h2>{$t('general')}</h2>
 | 
				
			||||||
        {#each shortcuts.general as shortcut}
 | 
					        <div class="text-sm">
 | 
				
			||||||
          <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
 | 
					          {#each shortcuts.general as shortcut}
 | 
				
			||||||
            <div class="flex justify-self-end">
 | 
					            <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
 | 
				
			||||||
              {#each shortcut.key as key}
 | 
					              <div class="flex justify-self-end">
 | 
				
			||||||
                <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
 | 
					                {#each shortcut.key as key}
 | 
				
			||||||
                  {key}
 | 
					                  <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
 | 
				
			||||||
                </p>
 | 
					                    {key}
 | 
				
			||||||
              {/each}
 | 
					                  </p>
 | 
				
			||||||
            </div>
 | 
					                {/each}
 | 
				
			||||||
            <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
					              </div>
 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        {/each}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="p-4">
 | 
					 | 
				
			||||||
      <h2>{$t('actions')}</h2>
 | 
					 | 
				
			||||||
      <div class="text-sm">
 | 
					 | 
				
			||||||
        {#each shortcuts.actions as shortcut}
 | 
					 | 
				
			||||||
          <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
 | 
					 | 
				
			||||||
            <div class="flex justify-self-end">
 | 
					 | 
				
			||||||
              {#each shortcut.key as key}
 | 
					 | 
				
			||||||
                <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
 | 
					 | 
				
			||||||
                  {key}
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
              {/each}
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <div class="flex items-center gap-2">
 | 
					 | 
				
			||||||
              <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
					              <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
				
			||||||
              {#if shortcut.info}
 | 
					 | 
				
			||||||
                <Icon path={mdiInformationOutline} title={shortcut.info} />
 | 
					 | 
				
			||||||
              {/if}
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          {/each}
 | 
				
			||||||
        {/each}
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    {/if}
 | 
				
			||||||
 | 
					    {#if shortcuts.actions.length > 0}
 | 
				
			||||||
 | 
					      <div class="p-4">
 | 
				
			||||||
 | 
					        <h2>{$t('actions')}</h2>
 | 
				
			||||||
 | 
					        <div class="text-sm">
 | 
				
			||||||
 | 
					          {#each shortcuts.actions as shortcut}
 | 
				
			||||||
 | 
					            <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
 | 
				
			||||||
 | 
					              <div class="flex justify-self-end">
 | 
				
			||||||
 | 
					                {#each shortcut.key as key}
 | 
				
			||||||
 | 
					                  <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
 | 
				
			||||||
 | 
					                    {key}
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                {/each}
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              <div class="flex items-center gap-2">
 | 
				
			||||||
 | 
					                <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
				
			||||||
 | 
					                {#if shortcut.info}
 | 
				
			||||||
 | 
					                  <Icon path={mdiInformationOutline} title={shortcut.info} />
 | 
				
			||||||
 | 
					                {/if}
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          {/each}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</FullScreenModal>
 | 
					</FullScreenModal>
 | 
				
			||||||
 | 
				
			|||||||
@ -64,8 +64,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<svelte:window
 | 
					<svelte:window
 | 
				
			||||||
  use:shortcuts={[
 | 
					  use:shortcuts={[
 | 
				
			||||||
    { shortcut: { key: 'k', shift: true }, onShortcut: onSelectAll },
 | 
					    { shortcut: { key: 'a' }, onShortcut: onSelectAll },
 | 
				
			||||||
    { shortcut: { key: 't', shift: true }, onShortcut: onSelectNone },
 | 
					    {
 | 
				
			||||||
 | 
					      shortcut: { key: 's' },
 | 
				
			||||||
 | 
					      onShortcut: () => {
 | 
				
			||||||
 | 
					        setAsset(assets[0]);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    { shortcut: { key: 'd' }, onShortcut: onSelectNone },
 | 
				
			||||||
    { shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
 | 
					    { shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
 | 
				
			||||||
  ]}
 | 
					  ]}
 | 
				
			||||||
/>
 | 
					/>
 | 
				
			||||||
 | 
				
			|||||||
@ -997,6 +997,7 @@
 | 
				
			|||||||
  "reset_password": "Reset password",
 | 
					  "reset_password": "Reset password",
 | 
				
			||||||
  "reset_people_visibility": "Reset people visibility",
 | 
					  "reset_people_visibility": "Reset people visibility",
 | 
				
			||||||
  "reset_to_default": "Reset to default",
 | 
					  "reset_to_default": "Reset to default",
 | 
				
			||||||
 | 
					  "resolve_duplicates": "Resolve duplicates",
 | 
				
			||||||
  "resolved_all_duplicates": "Resolved all duplicates",
 | 
					  "resolved_all_duplicates": "Resolved all duplicates",
 | 
				
			||||||
  "restore": "Restore",
 | 
					  "restore": "Restore",
 | 
				
			||||||
  "restore_all": "Restore all",
 | 
					  "restore_all": "Restore all",
 | 
				
			||||||
@ -1041,6 +1042,7 @@
 | 
				
			|||||||
  "see_all_people": "See all people",
 | 
					  "see_all_people": "See all people",
 | 
				
			||||||
  "select_album_cover": "Select album cover",
 | 
					  "select_album_cover": "Select album cover",
 | 
				
			||||||
  "select_all": "Select all",
 | 
					  "select_all": "Select all",
 | 
				
			||||||
 | 
					  "select_all_duplicates": "Select all duplicates",
 | 
				
			||||||
  "select_avatar_color": "Select avatar color",
 | 
					  "select_avatar_color": "Select avatar color",
 | 
				
			||||||
  "select_face": "Select face",
 | 
					  "select_face": "Select face",
 | 
				
			||||||
  "select_featured_photo": "Select featured photo",
 | 
					  "select_featured_photo": "Select featured photo",
 | 
				
			||||||
@ -1166,6 +1168,7 @@
 | 
				
			|||||||
  "unnamed_share": "Unnamed Share",
 | 
					  "unnamed_share": "Unnamed Share",
 | 
				
			||||||
  "unsaved_change": "Unsaved change",
 | 
					  "unsaved_change": "Unsaved change",
 | 
				
			||||||
  "unselect_all": "Unselect all",
 | 
					  "unselect_all": "Unselect all",
 | 
				
			||||||
 | 
					  "unselect_all_duplicates": "Unselect all duplicates",
 | 
				
			||||||
  "unstack": "Un-stack",
 | 
					  "unstack": "Un-stack",
 | 
				
			||||||
  "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
 | 
					  "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
 | 
				
			||||||
  "untracked_files": "Untracked files",
 | 
					  "untracked_files": "Untracked files",
 | 
				
			||||||
 | 
				
			|||||||
@ -13,10 +13,34 @@
 | 
				
			|||||||
  import type { PageData } from './$types';
 | 
					  import type { PageData } from './$types';
 | 
				
			||||||
  import { suggestDuplicateByFileSize } from '$lib/utils';
 | 
					  import { suggestDuplicateByFileSize } from '$lib/utils';
 | 
				
			||||||
  import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 | 
					  import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
 | 
				
			||||||
 | 
					  import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte';
 | 
				
			||||||
 | 
					  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
				
			||||||
 | 
					  import { mdiKeyboard } from '@mdi/js';
 | 
				
			||||||
  import { mdiCheckOutline, mdiTrashCanOutline } from '@mdi/js';
 | 
					  import { mdiCheckOutline, mdiTrashCanOutline } from '@mdi/js';
 | 
				
			||||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let data: PageData;
 | 
					  export let data: PageData;
 | 
				
			||||||
 | 
					  export let isShowKeyboardShortcut = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  interface Shortcuts {
 | 
				
			||||||
 | 
					    general: ExplainedShortcut[];
 | 
				
			||||||
 | 
					    actions: ExplainedShortcut[];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  interface ExplainedShortcut {
 | 
				
			||||||
 | 
					    key: string[];
 | 
				
			||||||
 | 
					    action: string;
 | 
				
			||||||
 | 
					    info?: string;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const duplicateShortcuts: Shortcuts = {
 | 
				
			||||||
 | 
					    general: [],
 | 
				
			||||||
 | 
					    actions: [
 | 
				
			||||||
 | 
					      { key: ['a'], action: $t('select_all_duplicates') },
 | 
				
			||||||
 | 
					      { key: ['s'], action: $t('view') },
 | 
				
			||||||
 | 
					      { key: ['d'], action: $t('unselect_all_duplicates') },
 | 
				
			||||||
 | 
					      { key: ['⇧', 'c'], action: $t('resolve_duplicates') },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: hasDuplicates = data.duplicates.length > 0;
 | 
					  $: hasDuplicates = data.duplicates.length > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -132,6 +156,11 @@
 | 
				
			|||||||
        {$t('keep_all')}
 | 
					        {$t('keep_all')}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </LinkButton>
 | 
					    </LinkButton>
 | 
				
			||||||
 | 
					    <CircleIconButton
 | 
				
			||||||
 | 
					      icon={mdiKeyboard}
 | 
				
			||||||
 | 
					      title={$t('show_keyboard_shortcuts')}
 | 
				
			||||||
 | 
					      on:click={() => (isShowKeyboardShortcut = !isShowKeyboardShortcut)}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="mt-4">
 | 
					  <div class="mt-4">
 | 
				
			||||||
@ -153,3 +182,7 @@
 | 
				
			|||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</UserPageLayout>
 | 
					</UserPageLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowKeyboardShortcut}
 | 
				
			||||||
 | 
					  <ShowShortcuts shortcuts={duplicateShortcuts} on:close={() => (isShowKeyboardShortcut = false)} />
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user