1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-28 06:32:44 +01:00

feat(web): allow ctrl-click / cmd-click on photos ()

* 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
web/src/lib
components
asset-viewer
assets/thumbnail
photos-page
shared-components/gallery-viewer
utils

View file

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

View file

@ -23,6 +23,7 @@
import ImageThumbnail from './image-thumbnail.svelte'; import ImageThumbnail from './image-thumbnail.svelte';
import VideoThumbnail from './video-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte';
import { shortcut } from '$lib/actions/shortcut'; import { shortcut } from '$lib/actions/shortcut';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
select: { asset: AssetResponseDto }; select: { asset: AssetResponseDto };
@ -36,6 +37,8 @@
export let thumbnailHeight: number | undefined = undefined; export let thumbnailHeight: number | undefined = undefined;
export let selected = false; export let selected = false;
export let selectionCandidate = false; export let selectionCandidate = false;
export let isMultiSelectState = false;
export let isStackSlideshow = false;
export let disabled = false; export let disabled = false;
export let readonly = false; export let readonly = false;
export let showArchiveIcon = false; export let showArchiveIcon = false;
@ -46,7 +49,6 @@
export { className as class }; export { className as class };
let mouseOver = false; let mouseOver = false;
$: clickable = !disabled && onClick;
$: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex }); $: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
@ -62,19 +64,30 @@
return [235, 235]; return [235, 235];
})(); })();
const thumbnailClickedHandler = () => { const thumbnailClickedHandler = (e: Event) => {
if (clickable) { e.stopPropagation();
e.preventDefault();
if (!disabled) {
onClick?.(asset); onClick?.(asset);
} }
}; };
const onIconClickedHandler = (e: MouseEvent) => { const onIconClickedHandler = (e: MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault();
if (!disabled) { if (!disabled) {
dispatch('select', { asset }); dispatch('select', { asset });
} }
}; };
const handleClick = (e: Event) => {
if (isMultiSelectState) {
onIconClickedHandler(e as MouseEvent);
} else if (isStackSlideshow) {
thumbnailClickedHandler(e);
}
};
const onMouseEnter = () => { const onMouseEnter = () => {
mouseOver = true; mouseOver = true;
}; };
@ -85,24 +98,22 @@
</script> </script>
<IntersectionObserver once={false} on:intersected let:intersecting> <IntersectionObserver once={false} on:intersected let:intersecting>
<!-- svelte-ignore a11y-no-noninteractive-tabindex --> <a
<div href={currentUrlReplaceAssetId(asset.id)}
style:width="{width}px" style:width="{width}px"
style:height="{height}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-gray-300'
: 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}" : 'bg-immich-primary/20 dark:bg-immich-dark-primary/20'}"
class:cursor-not-allowed={disabled} class:cursor-not-allowed={disabled}
class:hover:cursor-pointer={clickable}
on:mouseenter={onMouseEnter} on:mouseenter={onMouseEnter}
on:mouseleave={onMouseLeave} on:mouseleave={onMouseLeave}
role={clickable ? 'button' : undefined} tabindex={0}
tabindex={clickable ? 0 : undefined} on:click={handleClick}
on:click={thumbnailClickedHandler}
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: thumbnailClickedHandler }} use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: thumbnailClickedHandler }}
> >
{#if intersecting} {#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 --> <!-- Select asset button -->
{#if !readonly && (mouseOver || selected || selectionCandidate)} {#if !readonly && (mouseOver || selected || selectionCandidate)}
<button <button
@ -128,7 +139,7 @@
</div> </div>
<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:scale-[0.85]={selected}
class:rounded-xl={selected} class:rounded-xl={selected}
> >
@ -227,5 +238,5 @@
/> />
{/if} {/if}
{/if} {/if}
</div> </a>
</IntersectionObserver> </IntersectionObserver>

View file

@ -179,6 +179,7 @@
on:mouse-event={() => assetMouseEventHandler(groupTitle, asset)} on:mouse-event={() => assetMouseEventHandler(groupTitle, asset)}
selected={$selectedAssets.has(asset) || $assetStore.albumAssets.has(asset.id)} selected={$selectedAssets.has(asset) || $assetStore.albumAssets.has(asset.id)}
selectionCandidate={$assetSelectionCandidates.has(asset)} selectionCandidate={$assetSelectionCandidates.has(asset)}
isMultiSelectState={$isMultiSelectState || isSelectionMode}
disabled={$assetStore.albumAssets.has(asset.id)} disabled={$assetStore.albumAssets.has(asset.id)}
thumbnailWidth={box.width} thumbnailWidth={box.width}
thumbnailHeight={box.height} thumbnailHeight={box.height}

View file

@ -128,6 +128,7 @@
on:intersected={(event) => on:intersected={(event) =>
i === Math.max(1, assets.length - 7) ? dispatch('intersected', event.detail) : undefined} i === Math.max(1, assets.length - 7) ? dispatch('intersected', event.detail) : undefined}
selected={selectedAssets.has(asset)} selected={selectedAssets.has(asset)}
isMultiSelectState={isMultiSelectionMode}
{showArchiveIcon} {showArchiveIcon}
thumbnailWidth={geometry.boxes[i].width} thumbnailWidth={geometry.boxes[i].width}
thumbnailHeight={geometry.boxes[i].height} thumbnailHeight={geometry.boxes[i].height}

View file

@ -31,7 +31,7 @@ function currentUrlWithoutAsset() {
: $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search; : $page.url.pathname.replace(/(\/photos.*)$/, '') + $page.url.search;
} }
function currentUrlReplaceAssetId(assetId: string) { export function currentUrlReplaceAssetId(assetId: string) {
const $page = get(page); const $page = get(page);
// this contains special casing for the /photos/:assetId photos route, which hangs directly // this contains special casing for the /photos/:assetId photos route, which hangs directly
// off / instead of a subpath, unlike every other asset-containing route. // off / instead of a subpath, unlike every other asset-containing route.