mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	feat: "add to album" shortcut and generic menu option shortcuts (#15056)
* Add shortcut prop to MenuOption * Add "add to album" shortcut in photo grid
This commit is contained in:
		
							parent
							
								
									c148a28a82
								
							
						
					
					
						commit
						fa0b352bd0
					
				@ -16,6 +16,26 @@ export type ShortcutOptions<T = HTMLElement> = {
 | 
				
			|||||||
  preventDefault?: boolean;
 | 
					  preventDefault?: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const shortcutLabel = (shortcut: Shortcut) => {
 | 
				
			||||||
 | 
					  let label = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (shortcut.ctrl) {
 | 
				
			||||||
 | 
					    label += 'Ctrl ';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (shortcut.alt) {
 | 
				
			||||||
 | 
					    label += 'Alt ';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (shortcut.meta) {
 | 
				
			||||||
 | 
					    label += 'Cmd ';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (shortcut.shift) {
 | 
				
			||||||
 | 
					    label += '⇧';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  label += shortcut.key.toUpperCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return label;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Determines whether an event should be ignored. The event will be ignored if:
 | 
					/** Determines whether an event should be ignored. The event will be ignored if:
 | 
				
			||||||
 *  - The element dispatching the event is not the same as the element which the event listener is attached to
 | 
					 *  - The element dispatching the event is not the same as the element which the event listener is attached to
 | 
				
			||||||
 *  - The element dispatching the event is an input field
 | 
					 *  - The element dispatching the event is an input field
 | 
				
			||||||
 | 
				
			|||||||
@ -47,6 +47,7 @@
 | 
				
			|||||||
  onClick={() => (showAlbumPicker = true)}
 | 
					  onClick={() => (showAlbumPicker = true)}
 | 
				
			||||||
  text={shared ? $t('add_to_shared_album') : $t('add_to_album')}
 | 
					  text={shared ? $t('add_to_shared_album') : $t('add_to_album')}
 | 
				
			||||||
  icon={shared ? mdiShareVariantOutline : mdiImageAlbum}
 | 
					  icon={shared ? mdiShareVariantOutline : mdiImageAlbum}
 | 
				
			||||||
 | 
					  shortcut={{ key: 'l', shift: shared }}
 | 
				
			||||||
/>
 | 
					/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if showAlbumPicker}
 | 
					{#if showAlbumPicker}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,8 @@
 | 
				
			|||||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
  import { generateId } from '$lib/utils/generate-id';
 | 
					  import { generateId } from '$lib/utils/generate-id';
 | 
				
			||||||
  import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
 | 
					  import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
 | 
				
			||||||
 | 
					  import type { Shortcut } from '$lib/actions/shortcut';
 | 
				
			||||||
 | 
					  import { shortcutLabel as computeShortcutLabel, shortcut as bindShortcut } from '$lib/actions/shortcut';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  interface Props {
 | 
					  interface Props {
 | 
				
			||||||
    text: string;
 | 
					    text: string;
 | 
				
			||||||
@ -10,6 +12,8 @@
 | 
				
			|||||||
    activeColor?: string;
 | 
					    activeColor?: string;
 | 
				
			||||||
    textColor?: string;
 | 
					    textColor?: string;
 | 
				
			||||||
    onClick: () => void;
 | 
					    onClick: () => void;
 | 
				
			||||||
 | 
					    shortcut?: Shortcut | null;
 | 
				
			||||||
 | 
					    shortcutLabel?: string;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let {
 | 
					  let {
 | 
				
			||||||
@ -19,6 +23,8 @@
 | 
				
			|||||||
    activeColor = 'bg-slate-300',
 | 
					    activeColor = 'bg-slate-300',
 | 
				
			||||||
    textColor = 'text-immich-fg dark:text-immich-dark-bg',
 | 
					    textColor = 'text-immich-fg dark:text-immich-dark-bg',
 | 
				
			||||||
    onClick,
 | 
					    onClick,
 | 
				
			||||||
 | 
					    shortcut = null,
 | 
				
			||||||
 | 
					    shortcutLabel = '',
 | 
				
			||||||
  }: Props = $props();
 | 
					  }: Props = $props();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let id: string = generateId();
 | 
					  let id: string = generateId();
 | 
				
			||||||
@ -29,8 +35,17 @@
 | 
				
			|||||||
    $optionClickCallbackStore?.();
 | 
					    $optionClickCallbackStore?.();
 | 
				
			||||||
    onClick();
 | 
					    onClick();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (shortcut && !shortcutLabel) {
 | 
				
			||||||
 | 
					    shortcutLabel = computeShortcutLabel(shortcut);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const bindShortcutIfSet = shortcut
 | 
				
			||||||
 | 
					    ? (n: HTMLElement) => bindShortcut(n, { shortcut, onShortcut: onClick })
 | 
				
			||||||
 | 
					    : () => {};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svelte:window use:bindShortcutIfSet />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
 | 
					<!-- svelte-ignore a11y_click_events_have_key_events -->
 | 
				
			||||||
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
 | 
					<!-- svelte-ignore a11y_mouse_events_have_key_events -->
 | 
				
			||||||
<li
 | 
					<li
 | 
				
			||||||
@ -46,8 +61,15 @@
 | 
				
			|||||||
  {#if icon}
 | 
					  {#if icon}
 | 
				
			||||||
    <Icon path={icon} ariaHidden={true} size="18" />
 | 
					    <Icon path={icon} ariaHidden={true} size="18" />
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
  <div>
 | 
					  <div class="w-full">
 | 
				
			||||||
    {text}
 | 
					    <div class="flex justify-between">
 | 
				
			||||||
 | 
					      {text}
 | 
				
			||||||
 | 
					      {#if shortcutLabel}
 | 
				
			||||||
 | 
					        <span class="text-gray-500 pl-4">
 | 
				
			||||||
 | 
					          {shortcutLabel}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      {/if}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
    {#if subtitle}
 | 
					    {#if subtitle}
 | 
				
			||||||
      <p class="text-xs text-gray-500">
 | 
					      <p class="text-xs text-gray-500">
 | 
				
			||||||
        {subtitle}
 | 
					        {subtitle}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user