mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
feat(web): Add keyboard shortcut selection on grid (#16713)
* 15712: Added keyboard shortcuts for opening add to album modal and highlighting/selecting an album to add to. * 15712: Re-factored logic from template code into script. Extracted new album button into separate cmponent. * 15712: Document new keyboard shortucts now that they work everywhere. * 15712: Extract some constants/helper functions. * 15712: Missing comma. * 15712: Pulled logic out into separate unit testable class. * 15712: Added a unit test. * 15712: Move the modal back up to keep the github PR happy. * 15712: PR feedback - renamed typescript files and switch to class bind directive. * 15712:Move selection modal into correct package. * 15712: Better naming of module and files. * 15712: Add asset highlight using arrow keys. * 15172: Add escape behaviour everywhere. * 15712: Don't allow highlighting past start or end. * 15712: Clear the highlight on changes to the component state. * 15712: Use focus to track highlighted element. * 15712: Rename highlight -> focussed. * 15712: Better naming. * 15712: Cleanup. * 15712: Cleanup & simplify. * 15712: bugfix for clicking on button. * 15712: Cleanup. * 15712: Rollback unnecessary changes. * 15712: Add unit test. * 15712: Add thumbnail unit test. * 15712: Prettier. * 15712: Fix merge issue. * 15712: Add shortcut info. * 15712: Fix linter.
This commit is contained in:
parent
c80afea468
commit
b8acae2f21
10 changed files with 198 additions and 6 deletions
|
|
@ -40,6 +40,7 @@
|
|||
thumbnailWidth?: number | undefined;
|
||||
thumbnailHeight?: number | undefined;
|
||||
selected?: boolean;
|
||||
focussed?: boolean;
|
||||
selectionCandidate?: boolean;
|
||||
disabled?: boolean;
|
||||
readonly?: boolean;
|
||||
|
|
@ -60,7 +61,9 @@
|
|||
onRetrieveElement?: ((elment: HTMLElement) => void) | undefined;
|
||||
onSelect?: ((asset: AssetResponseDto) => void) | undefined;
|
||||
onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined;
|
||||
handleFocus?: (() => void) | undefined;
|
||||
class?: string;
|
||||
overrideDisplayForTest?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -72,6 +75,7 @@
|
|||
thumbnailWidth = undefined,
|
||||
thumbnailHeight = undefined,
|
||||
selected = false,
|
||||
focussed = false,
|
||||
selectionCandidate = false,
|
||||
disabled = false,
|
||||
readonly = false,
|
||||
|
|
@ -85,7 +89,9 @@
|
|||
onRetrieveElement = undefined,
|
||||
onSelect = undefined,
|
||||
onMouseEvent = undefined,
|
||||
handleFocus = undefined,
|
||||
class: className = '',
|
||||
overrideDisplayForTest = false,
|
||||
}: Props = $props();
|
||||
|
||||
let {
|
||||
|
|
@ -94,6 +100,7 @@
|
|||
|
||||
const componentId = generateId();
|
||||
let element: HTMLElement | undefined = $state();
|
||||
let focussableElement: HTMLElement | undefined = $state();
|
||||
let mouseOver = $state(false);
|
||||
let intersecting = $state(false);
|
||||
let lastRetrievedElement: HTMLElement | undefined = $state();
|
||||
|
|
@ -111,6 +118,12 @@
|
|||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (focussed && document.activeElement !== focussableElement) {
|
||||
focussableElement?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
let width = $derived(thumbnailSize || thumbnailWidth || 235);
|
||||
let height = $derived(thumbnailSize || thumbnailHeight || 235);
|
||||
let display = $derived(intersecting);
|
||||
|
|
@ -217,7 +230,7 @@
|
|||
></canvas>
|
||||
{/if}
|
||||
|
||||
{#if display}
|
||||
{#if display || overrideDisplayForTest}
|
||||
<!-- svelte queries for all links on afterNavigate, leading to performance problems in asset-grid which updates
|
||||
the navigation url on scroll. Replace this with button for now. -->
|
||||
<div
|
||||
|
|
@ -226,14 +239,20 @@
|
|||
class:cursor-pointer={!disabled}
|
||||
onmouseenter={onMouseEnter}
|
||||
onmouseleave={onMouseLeave}
|
||||
onkeypress={(evt) => {
|
||||
onkeydown={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
callClickHandlers();
|
||||
}
|
||||
if (evt.key === 'x') {
|
||||
onSelect?.(asset);
|
||||
}
|
||||
}}
|
||||
tabindex={0}
|
||||
onclick={handleClick}
|
||||
role="link"
|
||||
bind:this={focussableElement}
|
||||
onfocus={handleFocus}
|
||||
data-testid="container-with-tabindex"
|
||||
>
|
||||
{#if mouseOver && !disableMouseOver}
|
||||
<!-- lazy show the url on mouse over-->
|
||||
|
|
@ -244,7 +263,7 @@
|
|||
style:height="{height}px"
|
||||
href={currentUrlReplaceAssetId(asset.id)}
|
||||
onclick={(evt) => evt.preventDefault()}
|
||||
tabindex={0}
|
||||
tabindex={-1}
|
||||
aria-label="Thumbnail URL"
|
||||
>
|
||||
</a>
|
||||
|
|
@ -258,6 +277,8 @@
|
|||
class="absolute p-2 focus:outline-none"
|
||||
class:cursor-not-allowed={disabled}
|
||||
role="checkbox"
|
||||
tabindex={-1}
|
||||
onfocus={handleFocus}
|
||||
aria-checked={selected}
|
||||
{disabled}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue