1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-16 00:36:47 +01:00

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>
This commit is contained in:
George Shao 2024-06-08 16:33:23 -04:00 committed by GitHub
parent d8d64ecc45
commit 4d862525bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 28 additions and 14 deletions

View file

@ -740,6 +740,7 @@
readonly
thumbnailSize={stackedAsset.id == asset.id ? 65 : 60}
showStackedIcon={false}
isStackSlideshow={true}
/>
{#if stackedAsset.id == asset.id}

View file

@ -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>

View file

@ -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}

View file

@ -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}

View file

@ -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.