From 4d862525bcb5f00f8160e3308a9c09b60bb2a746 Mon Sep 17 00:00:00 2001 From: George Shao <GeorgeShao123@gmail.com> Date: Sat, 8 Jun 2024 16:33:23 -0400 Subject: [PATCH] feat(web): allow ctrl-click / cmd-click on photos (#9954) * feat(web): allow ctrl-click / cmd-click on photos * fix: photo opening when deselected bug * fix: consistent naming * remove redundant code * fix: disabled picture is clickable in "add to album" grid * remove unnecessary code * cleanup * fix file permissions * fix: album selection bug * fix: stack slideshow bug & search gallery viewer bug * cleanup * fix dark mode stack slideshow bug * cleanup --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com> --- .../asset-viewer/asset-viewer.svelte | 1 + .../assets/thumbnail/thumbnail.svelte | 37 ++++++++++++------- .../photos-page/asset-date-group.svelte | 1 + .../gallery-viewer/gallery-viewer.svelte | 1 + web/src/lib/utils/navigation.ts | 2 +- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 3518528986..a7e4b87195 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -740,6 +740,7 @@ readonly thumbnailSize={stackedAsset.id == asset.id ? 65 : 60} showStackedIcon={false} + isStackSlideshow={true} /> {#if stackedAsset.id == asset.id} diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index bd2dbd6fff..5c86801160 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -23,6 +23,7 @@ import ImageThumbnail from './image-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte'; import { shortcut } from '$lib/actions/shortcut'; + import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; const dispatch = createEventDispatcher<{ select: { asset: AssetResponseDto }; @@ -36,6 +37,8 @@ export let thumbnailHeight: number | undefined = undefined; export let selected = false; export let selectionCandidate = false; + export let isMultiSelectState = false; + export let isStackSlideshow = false; export let disabled = false; export let readonly = false; export let showArchiveIcon = false; @@ -46,7 +49,6 @@ export { className as class }; let mouseOver = false; - $: clickable = !disabled && onClick; $: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex }); @@ -62,19 +64,30 @@ return [235, 235]; })(); - const thumbnailClickedHandler = () => { - if (clickable) { + const thumbnailClickedHandler = (e: Event) => { + e.stopPropagation(); + e.preventDefault(); + if (!disabled) { onClick?.(asset); } }; const onIconClickedHandler = (e: MouseEvent) => { e.stopPropagation(); + e.preventDefault(); if (!disabled) { dispatch('select', { asset }); } }; + const handleClick = (e: Event) => { + if (isMultiSelectState) { + onIconClickedHandler(e as MouseEvent); + } else if (isStackSlideshow) { + thumbnailClickedHandler(e); + } + }; + const onMouseEnter = () => { mouseOver = true; }; @@ -85,24 +98,22 @@ </script> <IntersectionObserver once={false} on:intersected let:intersecting> - <!-- svelte-ignore a11y-no-noninteractive-tabindex --> - <div + <a + href={currentUrlReplaceAssetId(asset.id)} style:width="{width}px" style:height="{height}px" - class="group focus-visible:outline-none relative overflow-hidden {disabled + class="group focus-visible:outline-none flex overflow-hidden {disabled ? 'bg-gray-300' : 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}" class:cursor-not-allowed={disabled} - class:hover:cursor-pointer={clickable} on:mouseenter={onMouseEnter} on:mouseleave={onMouseLeave} - role={clickable ? 'button' : undefined} - tabindex={clickable ? 0 : undefined} - on:click={thumbnailClickedHandler} + tabindex={0} + on:click={handleClick} use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: thumbnailClickedHandler }} > {#if intersecting} - <div class="absolute z-20 h-full w-full {className}"> + <div class="absolute z-20 {className}" style:width="{width}px" style:height="{height}px"> <!-- Select asset button --> {#if !readonly && (mouseOver || selected || selectionCandidate)} <button @@ -128,7 +139,7 @@ </div> <div - class="absolute h-full w-full select-none bg-gray-100 transition-transform dark:bg-immich-dark-gray" + class="absolute h-full w-full select-none bg-transparent transition-transform" class:scale-[0.85]={selected} class:rounded-xl={selected} > @@ -227,5 +238,5 @@ /> {/if} {/if} - </div> + </a> </IntersectionObserver> diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index c6b9998d85..d7aefa24ec 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -179,6 +179,7 @@ on:mouse-event={() => assetMouseEventHandler(groupTitle, asset)} selected={$selectedAssets.has(asset) || $assetStore.albumAssets.has(asset.id)} selectionCandidate={$assetSelectionCandidates.has(asset)} + isMultiSelectState={$isMultiSelectState || isSelectionMode} disabled={$assetStore.albumAssets.has(asset.id)} thumbnailWidth={box.width} thumbnailHeight={box.height} diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 8afebc50db..27bd088a80 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -128,6 +128,7 @@ on:intersected={(event) => i === Math.max(1, assets.length - 7) ? dispatch('intersected', event.detail) : undefined} selected={selectedAssets.has(asset)} + isMultiSelectState={isMultiSelectionMode} {showArchiveIcon} thumbnailWidth={geometry.boxes[i].width} thumbnailHeight={geometry.boxes[i].height} diff --git a/web/src/lib/utils/navigation.ts b/web/src/lib/utils/navigation.ts index 356ea888a4..4d5660f173 100644 --- a/web/src/lib/utils/navigation.ts +++ b/web/src/lib/utils/navigation.ts @@ -31,7 +31,7 @@ function currentUrlWithoutAsset() { : $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search; } -function currentUrlReplaceAssetId(assetId: string) { +export function currentUrlReplaceAssetId(assetId: string) { const $page = get(page); // this contains special casing for the /photos/:assetId photos route, which hangs directly // off / instead of a subpath, unlike every other asset-containing route.