1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-27 22:22:45 +01:00

feat(web): scrollable context menus

This commit is contained in:
ben-basten 2024-09-09 23:29:09 -04:00
parent 971ba63447
commit e628e6a807
No known key found for this signature in database
GPG key ID: 78803E894B258348
15 changed files with 42 additions and 44 deletions
web/src
lib
routes
(user)
archive/[[photos=photos]]/[[assetId=id]]
partners/[userId]/[[photos=photos]]/[[assetId=id]]
people/[personId]/[[photos=photos]]/[[assetId=id]]
search/[[photos=photos]]/[[assetId=id]]
admin/library-management

View file

@ -46,7 +46,11 @@ export const contextMenuNavigation: Action<HTMLElement, Options> = (node, option
}; };
const moveSelection = async (direction: 'up' | 'down', event: KeyboardEvent) => { const moveSelection = async (direction: 'up' | 'down', event: KeyboardEvent) => {
const { selectionChanged, container, openDropdown } = options; const { selectionChanged, container, openDropdown, isOpen } = options;
if (!isOpen) {
// reset the scroll position before opening the menu
container?.scrollTo({ top: 0 });
}
if (openDropdown) { if (openDropdown) {
openDropdown(event); openDropdown(event);
await tick(); await tick();

View file

@ -109,7 +109,13 @@
{/if} {/if}
</div> </div>
{#if isOwned} {#if isOwned}
<ButtonContextMenu icon={mdiDotsVertical} size="20" title={$t('options')}> <ButtonContextMenu
icon={mdiDotsVertical}
size="20"
title={$t('options')}
direction="right"
align="top-left"
>
{#if role === AlbumUserRole.Viewer} {#if role === AlbumUserRole.Viewer}
<MenuOption onClick={() => handleSetReadonly(user, AlbumUserRole.Editor)} text={$t('allow_edits')} /> <MenuOption onClick={() => handleSetReadonly(user, AlbumUserRole.Editor)} text={$t('allow_edits')} />
{:else} {:else}

View file

@ -186,13 +186,7 @@
{/if} {/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id} {#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="mr-4"> <div class="mr-4">
<ButtonContextMenu <ButtonContextMenu icon={mdiDotsVertical} title={$t('comment_options')} size="16">
icon={mdiDotsVertical}
title={$t('comment_options')}
align="top-right"
direction="left"
size="16"
>
<MenuOption <MenuOption
activeColor="bg-red-200" activeColor="bg-red-200"
icon={mdiDeleteOutline} icon={mdiDeleteOutline}
@ -239,13 +233,7 @@
{/if} {/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id} {#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="mr-4"> <div class="mr-4">
<ButtonContextMenu <ButtonContextMenu icon={mdiDotsVertical} title={$t('reaction_options')} size="16">
icon={mdiDotsVertical}
title={$t('reaction_options')}
align="top-right"
direction="left"
size="16"
>
<MenuOption <MenuOption
activeColor="bg-red-200" activeColor="bg-red-200"
icon={mdiDeleteOutline} icon={mdiDeleteOutline}

View file

@ -128,7 +128,7 @@
{#if isOwner} {#if isOwner}
<DeleteAction {asset} {onAction} /> <DeleteAction {asset} {onAction} />
<ButtonContextMenu direction="left" align="top-right" color="opaque" title={$t('more')} icon={mdiDotsVertical}> <ButtonContextMenu color="opaque" title={$t('more')} icon={mdiDotsVertical}>
{#if showSlideshow} {#if showSlideshow}
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} /> <MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if} {/if}

View file

@ -67,6 +67,8 @@
size="20" size="20"
icon={mdiDotsVertical} icon={mdiDotsVertical}
title={$t('show_person_options')} title={$t('show_person_options')}
direction="right"
align="top-left"
> >
<MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} /> <MenuOption onClick={onHidePerson} icon={mdiEyeOffOutline} text={$t('hide_person')} />
<MenuOption onClick={onChangeName} icon={mdiAccountEditOutline} text={$t('change_name')} /> <MenuOption onClick={onChangeName} icon={mdiAccountEditOutline} text={$t('change_name')} />

View file

@ -237,7 +237,7 @@
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={handleUpdate} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={handleUpdate} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />

View file

@ -20,11 +20,11 @@
/** /**
* The alignment of the context menu relative to the button. * The alignment of the context menu relative to the button.
*/ */
export let align: Align = 'top-left'; export let align: Align = 'top-right';
/** /**
* The direction in which the context menu should open. * The direction in which the context menu should open.
*/ */
export let direction: 'left' | 'right' = 'right'; export let direction: 'left' | 'right' = 'left';
export let color: Color = 'transparent'; export let color: Color = 'transparent';
export let size: string | undefined = undefined; export let size: string | undefined = undefined;
export let padding: Padding | undefined = undefined; export let padding: Padding | undefined = undefined;

View file

@ -18,25 +18,26 @@
let left: number; let left: number;
let top: number; let top: number;
// We need to bind clientHeight since the bounding box may return a height
// of zero when starting the 'slide' animation.
let height: number;
$: { $: {
if (menuElement) { if (menuElement) {
const rect = menuElement.getBoundingClientRect(); const rect = menuElement.getBoundingClientRect();
const directionWidth = direction === 'left' ? rect.width : 0; const directionWidth = direction === 'left' ? rect.width : 0;
const menuHeight = Math.min(menuElement.clientHeight, height) || 0; const menuHeight = menuElement.clientHeight || 0;
left = Math.min(window.innerWidth - rect.width, x - directionWidth); const calcLeft = Math.min(window.innerWidth - rect.width, x - directionWidth);
left = Math.max(0, calcLeft);
top = Math.min(window.innerHeight - menuHeight, y); top = Math.min(window.innerHeight - menuHeight, y);
} }
} }
</script> </script>
<div <div
bind:clientHeight={height} class="fixed z-10 overflow-hidden rounded-lg duration-[250ms] ease-in {isVisible
class="fixed z-10 min-w-[200px] w-max max-w-[300px] overflow-hidden rounded-lg shadow-lg" ? 'shadow-lg transition-shadow'
: 'shadow-none transition-none'}"
class:shadow-none={!isVisible}
class:shadow-lg={isVisible}
class:transition-none={!isVisible}
style:left="{left}px" style:left="{left}px"
style:top="{top}px" style:top="{top}px"
transition:slide={{ duration: 250, easing: quintOut }} transition:slide={{ duration: 250, easing: quintOut }}
@ -48,9 +49,9 @@
aria-label={ariaLabel} aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy} aria-labelledby={ariaLabelledBy}
bind:this={menuElement} bind:this={menuElement}
class:max-h-[100vh]={isVisible} class="flex flex-col transition-all duration-[250ms] ease-in-out outline-none immich-scrollbar bg-slate-100 relative min-w-[200px] max-w-[200px] sm:max-w-[256px] rounded-lg {isVisible
class:max-h-0={!isVisible} ? 'translate-x-0 max-h-dvh overflow-y-auto'
class="flex flex-col transition-all duration-[250ms] ease-in-out outline-none" : `${direction === 'left' ? 'translate-x-28' : '-translate-x-28'} max-h-0 overflow-y-hidden`}"
role="menu" role="menu"
tabindex="-1" tabindex="-1"
> >

View file

@ -33,7 +33,9 @@
role="menuitem" role="menuitem"
> >
{#if icon} {#if icon}
<Icon path={icon} ariaHidden={true} size="18" /> <div class="flex-none">
<Icon path={icon} ariaHidden={true} size="18" />
</div>
{/if} {/if}
<div> <div>
{text} {text}

View file

@ -107,6 +107,8 @@
size="24" size="24"
padding="3" padding="3"
hideContent hideContent
direction="right"
align="top-left"
> >
<SharedLinkEdit menuItem {onEdit} /> <SharedLinkEdit menuItem {onEdit} />
<SharedLinkCopy menuItem {link} /> <SharedLinkCopy menuItem {link} />

View file

@ -42,7 +42,7 @@
<AddToAlbum shared /> <AddToAlbum shared />
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} /> <DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
</ButtonContextMenu> </ButtonContextMenu>

View file

@ -31,7 +31,7 @@
{#if $isMultiSelectState} {#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={clearMultiselect}> <AssetSelectControlBar assets={$selectedAssets} clearSelect={clearMultiselect}>
<CreateSharedLink /> <CreateSharedLink />
<ButtonContextMenu icon={mdiPlus} title={$t('add')}> <ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</ButtonContextMenu> </ButtonContextMenu>

View file

@ -385,7 +385,7 @@
<AddToAlbum shared /> <AddToAlbum shared />
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem filename="{person.name || 'immich'}.zip" /> <DownloadAction menuItem filename="{person.name || 'immich'}.zip" />
<MenuOption <MenuOption
icon={mdiAccountMultipleCheckOutline} icon={mdiAccountMultipleCheckOutline}

View file

@ -235,7 +235,7 @@
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />

View file

@ -285,14 +285,7 @@
</td> </td>
<td class=" text-ellipsis px-4 text-sm"> <td class=" text-ellipsis px-4 text-sm">
<ButtonContextMenu <ButtonContextMenu color="primary" size="16" icon={mdiDotsVertical} title={$t('library_options')}>
align="top-right"
direction="left"
color="primary"
size="16"
icon={mdiDotsVertical}
title={$t('library_options')}
>
<MenuOption onClick={() => onScanClicked(library)} text={$t('scan_library')} /> <MenuOption onClick={() => onScanClicked(library)} text={$t('scan_library')} />
<hr /> <hr />
<MenuOption onClick={() => onRenameClicked(index)} text={$t('rename')} /> <MenuOption onClick={() => onRenameClicked(index)} text={$t('rename')} />