fix: z-index overuse (#18192)

This commit is contained in:
Daniel Dietzler 2025-05-13 16:10:05 +02:00 committed by GitHub
parent 48112d84a3
commit 989d9dbe51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 330 additions and 332 deletions

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { user } from '$lib/stores/user.store';
import type { AlbumResponseDto } from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js';
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
import { getShortDateRange } from '$lib/utils/date-time';
import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { user } from '$lib/stores/user.store';
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
import { getShortDateRange } from '$lib/utils/date-time';
import type { AlbumResponseDto } from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
@ -40,7 +40,7 @@
{#if onShowContextMenu}
<div
id="icon-{album.id}"
class="absolute end-6 top-6 z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100"
class="absolute end-6 top-6 opacity-0 group-hover:opacity-100 focus-within:opacity-100"
data-testid="context-button-parent"
>
<CircleIconButton

View File

@ -58,6 +58,32 @@
}}
/>
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)]">
<AssetGrid enableRouting={true} {album} {assetStore} {assetInteraction}>
<section class="pt-8 md:pt-24 px-2 md:px-0">
<!-- ALBUM TITLE -->
<h1
class="text-2xl md:text-4xl lg:text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary"
>
{album.albumName}
</h1>
{#if album.assetCount > 0}
<AlbumSummary {album} />
{/if}
<!-- ALBUM DESCRIPTION -->
{#if album.description}
<p
class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-start font-medium text-base text-black dark:text-gray-300"
>
{album.description}
</p>
{/if}
</section>
</AssetGrid>
</main>
<header>
{#if assetInteraction.selectionActive}
<AssetSelectControlBar
@ -100,29 +126,3 @@
</ControlAppBar>
{/if}
</header>
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)]">
<AssetGrid enableRouting={true} {album} {assetStore} {assetInteraction}>
<section class="pt-8 md:pt-24 px-2 md:px-0">
<!-- ALBUM TITLE -->
<h1
class="text-2xl md:text-4xl lg:text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary"
>
{album.albumName}
</h1>
{#if album.assetCount > 0}
<AlbumSummary {album} />
{/if}
<!-- ALBUM DESCRIPTION -->
{#if album.description}
<p
class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-start font-medium text-base text-black dark:text-gray-300"
>
{album.description}
</p>
{/if}
</section>
</AssetGrid>
</main>

View File

@ -1,10 +1,10 @@
<script lang="ts">
import { getAssetThumbnailUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import { normalizeSearchString } from '$lib/utils/string-utils.js';
import AlbumListItemDetails from './album-list-item-details.svelte';
import type { Action } from 'svelte/action';
import { SCROLL_PROPERTIES } from '$lib/components/shared-components/album-selection/album-selection-utils';
import { getAssetThumbnailUrl } from '$lib/utils';
import { normalizeSearchString } from '$lib/utils/string-utils.js';
import { type AlbumResponseDto } from '@immich/sdk';
import type { Action } from 'svelte/action';
import AlbumListItemDetails from './album-list-item-details.svelte';
interface Props {
album: AlbumResponseDto;
@ -52,7 +52,7 @@
<img
src={getAssetThumbnailUrl(album.albumThumbnailAssetId)}
alt={album.albumName}
class="z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg"
class="h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg"
data-testid="album-image"
draggable="false"
/>

View File

@ -16,7 +16,7 @@
{#if downloadManager.isDownloading}
<div
transition:fly={{ x: -100, duration: 350 }}
class="fixed bottom-10 start-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border p-4 text-sm shadow-sm bg-light"
class="fixed bottom-10 start-2 max-h-[270px] w-[315px] rounded-2xl border p-4 text-sm shadow-sm bg-light"
>
<p class="mb-2 text-xs text-gray-500">{$t('downloading').toUpperCase()}</p>
<div class="my-2 mb-2 flex max-h-[200px] flex-col overflow-y-auto text-sm">

View File

@ -1,15 +1,10 @@
<script lang="ts">
import { onMount, onDestroy, tick } from 'svelte';
import { t } from 'svelte-i18n';
import { getAssetOriginalUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { getAltText } from '$lib/utils/thumbnail-util';
import { onDestroy, onMount, tick } from 'svelte';
import { t } from 'svelte-i18n';
import { imgElement, cropAreaEl, resetCropStore, overlayEl, isResizingOrDragging, cropFrame } from './crop-store';
import { draw } from './drawing';
import { onImageLoad, resizeCanvas } from './image-loading';
import { handleMouseDown, handleMouseMove, handleMouseUp } from './mouse-handlers';
import { recalculateCrop, animateCropChange } from './crop-settings';
import {
changedOriention,
cropAspectRatio,
@ -18,6 +13,11 @@
rotateDegrees,
} from '$lib/stores/asset-editor.store';
import type { AssetResponseDto } from '@immich/sdk';
import { animateCropChange, recalculateCrop } from './crop-settings';
import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store';
import { draw } from './drawing';
import { onImageLoad, resizeCanvas } from './image-loading';
import { handleMouseDown, handleMouseMove, handleMouseUp } from './mouse-handlers';
interface Props {
asset: AssetResponseDto;
@ -169,7 +169,6 @@
border: 2px solid white;
box-sizing: border-box;
pointer-events: none;
z-index: 1;
}
.corner {

View File

@ -214,7 +214,7 @@
<img
src={assetFileUrl}
alt={$getAltText(asset)}
class="absolute top-0 start-0 -z-10 object-cover h-full w-full blur-lg"
class="absolute top-0 start-0 object-cover h-full w-full blur-lg"
draggable="false"
/>
{/if}

View File

@ -5,8 +5,8 @@
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { TUNABLES } from '$lib/utils/tunables';
import { mdiEyeOffOutline } from '@mdi/js';
import type { ClassValue } from 'svelte/elements';
import type { ActionReturn } from 'svelte/action';
import type { ClassValue } from 'svelte/elements';
import { fade } from 'svelte/transition';
interface Props {

View File

@ -215,7 +215,7 @@
slow: ??ms
-->
<div
class={['group absolute top-[0px] bottom-[0px]', { 'curstor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
class={['group absolute top-[0px] bottom-[0px]', { 'cursor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
style:width="inherit"
style:height="inherit"
onmouseenter={onMouseEnter}
@ -239,42 +239,6 @@
tabindex={0}
role="link"
>
<!-- Select asset button -->
{#if !usingMobileDevice && mouseOver && !disableLinkMouseOver}
<!-- lazy show the url on mouse over-->
<a
class={['absolute z-10 w-full top-0 bottom-0']}
style:cursor="unset"
href={currentUrlReplaceAssetId(asset.id)}
onclick={(evt) => evt.preventDefault()}
tabindex={-1}
aria-label="Thumbnail URL"
>
</a>
{/if}
{#if !readonly && (mouseOver || selected || selectionCandidate)}
<button
type="button"
onclick={onIconClickedHandler}
class={['absolute z-20 p-2 focus:outline-none', { 'cursor-not-allowed': disabled }]}
role="checkbox"
tabindex={-1}
onfocus={handleFocus}
aria-checked={selected}
{disabled}
>
{#if disabled}
<Icon path={mdiCheckCircle} size="24" class="text-zinc-800" />
{:else if selected}
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
<Icon path={mdiCheckCircle} size="24" class="text-immich-primary" />
</div>
{:else}
<Icon path={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
{/if}
</button>
{/if}
<div
class={[
'absolute h-full w-full select-none bg-transparent transition-transform',
@ -284,6 +248,19 @@
>
<!-- icon overlay -->
<div>
{#if !usingMobileDevice && mouseOver && !disableLinkMouseOver}
<!-- lazy show the url on mouse over-->
<a
class="absolute w-full top-0 bottom-0"
style:cursor="unset"
href={currentUrlReplaceAssetId(asset.id)}
onclick={(evt) => evt.preventDefault()}
tabindex={-1}
aria-label="Thumbnail URL"
>
</a>
{/if}
<!-- Gradient overlay on hover -->
{#if !usingMobileDevice && !disabled}
<div
@ -293,10 +270,10 @@
]}
></div>
{/if}
<!-- Dimmed support -->
<!-- Dimmed support -->
{#if dimmed && !mouseOver}
<div id="a" class={['absolute h-full w-full z-30 bg-gray-700/40', { 'rounded-xl': selected }]}></div>
<div id="a" class={['absolute h-full w-full bg-gray-700/40', { 'rounded-xl': selected }]}></div>
{/if}
<!-- Outline on focus -->
<div
@ -308,19 +285,19 @@
<!-- Favorite asset star -->
{#if !authManager.key && asset.isFavorite}
<div class="absolute bottom-2 start-2 z-10">
<div class="absolute bottom-2 start-2">
<Icon path={mdiHeart} size="24" class="text-white" />
</div>
{/if}
{#if !authManager.key && showArchiveIcon && asset.isArchived}
<div class={['absolute start-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
</div>
{/if}
{#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
<div class="absolute end-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white">
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
<span class="pe-2 pt-2">
<Icon path={mdiRotate360} size="24" />
</span>
@ -331,7 +308,7 @@
{#if asset.stack && showStackedIcon}
<div
class={[
'absolute z-10 flex place-items-center gap-1 text-xs font-medium text-white',
'absolute flex place-items-center gap-1 text-xs font-medium text-white',
asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 end-0' : 'top-7 end-1',
]}
>
@ -382,5 +359,29 @@
out:fade={{ duration: 100 }}
></div>
{/if}
<!-- Select asset button -->
{#if !readonly && (mouseOver || selected || selectionCandidate)}
<button
type="button"
onclick={onIconClickedHandler}
class={['absolute p-2 focus:outline-none', { 'cursor-not-allowed': disabled }]}
role="checkbox"
tabindex={-1}
onfocus={handleFocus}
aria-checked={selected}
{disabled}
>
{#if disabled}
<Icon path={mdiCheckCircle} size="24" class="text-zinc-800" />
{:else if selected}
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
<Icon path={mdiCheckCircle} size="24" class="text-immich-primary" />
</div>
{:else}
<Icon path={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
{/if}
</button>
{/if}
</div>
</div>

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { Duration } from 'luxon';
import Icon from '$lib/components/elements/icon.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { mdiAlertCircleOutline, mdiPauseCircleOutline, mdiPlayCircleOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
import { Duration } from 'luxon';
interface Props {
url: string;
@ -55,7 +55,7 @@
};
</script>
<div class="absolute end-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
{#if showTime}
<span class="pt-2">
{#if remainingSeconds < 60}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { getTabbable } from '$lib/utils/focus-util';
import { t } from 'svelte-i18n';
import Button from './button.svelte';
import { getTabbable } from '$lib/utils/focus-util';
interface Props {
/**
@ -56,7 +56,7 @@
};
</script>
<div class="absolute z-50 top-2 start-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
<div class="absolute top-2 start-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
<Button
size="sm"
rounded="none"

View File

@ -108,7 +108,7 @@
{#if showMenu}
<div
transition:fly={{ y: -30, duration: 250 }}
class="text-sm font-medium absolute z-50 flex min-w-[250px] max-h-[70vh] overflow-y-auto immich-scrollbar flex-col rounded-2xl bg-gray-100 py-2 text-black shadow-lg dark:bg-gray-700 dark:text-white {className} {getAlignClass(
class="text-sm font-medium z-[1] absolute flex min-w-[250px] max-h-[70vh] overflow-y-auto immich-scrollbar flex-col rounded-2xl bg-gray-100 py-2 text-black shadow-lg dark:bg-gray-700 dark:text-white {className} {getAlignClass(
position,
)}"
>

View File

@ -74,7 +74,7 @@
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2001] h-full w-[360px] overflow-x-hidden p-2 dark:text-immich-dark-fg"
class="absolute top-0 h-full w-[360px] overflow-x-hidden p-2 dark:text-immich-dark-fg"
>
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}

View File

@ -112,7 +112,7 @@
<svelte:window use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: onClose }} />
<div
class="fixed top-0 z-10 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
class="fixed top-0 flex h-16 w-full items-center justify-between border-b bg-white p-1 dark:border-immich-dark-gray dark:bg-black dark:text-immich-dark-fg md:p-8"
>
<div class="flex items-center">
<CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} />

View File

@ -96,7 +96,7 @@
<section
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute start-0 top-0 z-[9999] h-full w-full bg-light"
class="absolute start-0 top-0 h-full w-full bg-light"
>
<ControlAppBar onClose={onBack}>
{#snippet leading()}

View File

@ -193,7 +193,7 @@
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2000] h-full w-[360px] overflow-x-hidden p-2 dark:text-immich-dark-fg bg-light"
class="absolute top-0 h-full w-[360px] overflow-x-hidden p-2 dark:text-immich-dark-fg bg-light"
>
<div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2">
@ -222,7 +222,7 @@
{:else}
{#each peopleWithFaces as face, index (face.id)}
{@const personName = face.person ? face.person?.name : $t('face_unassigned')}
<div class="relative z-[20001] h-[115px] w-[95px]">
<div class="relative h-[115px] w-[95px]">
<div
role="button"
tabindex={index}

View File

@ -120,7 +120,7 @@
<section
transition:fly={{ y: 500, duration: 100, easing: quintOut }}
class="absolute start-0 top-0 z-[9999] h-full w-full bg-light"
class="absolute start-0 top-0 h-full w-full bg-light"
>
<ControlAppBar {onClose}>
{#snippet leading()}

View File

@ -11,11 +11,7 @@
<section class="min-w-dvw flex min-h-dvh items-center justify-center relative">
<div class="absolute -z-10 w-full h-full flex place-items-center place-content-center">
<img
src={immichLogo}
class="max-w-screen-md mx-auto h-full mb-2 antialiased -z-10 overflow-hidden"
alt="Immich logo"
/>
<img src={immichLogo} class="max-w-screen-md mx-auto h-full mb-2 antialiased overflow-hidden" alt="Immich logo" />
<div
class="w-full h-[99%] absolute start-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20"
></div>

View File

@ -4,11 +4,11 @@
<script lang="ts">
import { useActions, type ActionArray } from '$lib/actions/use-actions';
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
import AdminSideBar from '$lib/components/shared-components/side-bar/admin-side-bar.svelte';
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import type { Snippet } from 'svelte';
import NavigationBar from '../shared-components/navigation-bar/navigation-bar.svelte';
import AdminSideBar from '../shared-components/side-bar/admin-side-bar.svelte';
import SideBar from '../shared-components/side-bar/side-bar.svelte';
interface Props {
hideNavbar?: boolean;
@ -51,18 +51,24 @@
</header>
<div
tabindex="-1"
class="relative grid grid-cols-[theme(spacing.0)_auto] overflow-hidden sidebar:grid-cols-[theme(spacing.64)_auto]
class="relative z-0 grid grid-cols-[theme(spacing.0)_auto] overflow-hidden sidebar:grid-cols-[theme(spacing.64)_auto]
{hideNavbar ? 'h-dvh' : 'h-[calc(100dvh-var(--navbar-height))]'}
{hideNavbar ? 'pt-[var(--navbar-height)]' : ''}
{hideNavbar ? 'max-md:pt-[var(--navbar-height-md)]' : ''}"
>
{#if sidebar}{@render sidebar()}{:else if admin}
{#if sidebar}
{@render sidebar()}
{:else if admin}
<AdminSideBar />
{:else}
<SideBar />
{/if}
<main class="relative">
<div class="{scrollbarClass} absolute {hasTitleClass} w-full overflow-y-auto" use:useActions={use}>
{@render children?.()}
</div>
{#if title || buttons}
<div
class="absolute flex h-16 w-full place-items-center justify-between border-b p-2 dark:border-immich-dark-gray dark:text-immich-dark-fg"
@ -78,9 +84,5 @@
{@render buttons?.()}
</div>
{/if}
<div class="{scrollbarClass} absolute {hasTitleClass} w-full overflow-y-auto" use:useActions={use}>
{@render children?.()}
</div>
</main>
</div>

View File

@ -305,7 +305,7 @@
/>
{#if assetInteraction.selectionActive}
<div class="sticky top-0 z-[90]">
<div class="sticky top-0">
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => cancelMultiselect(assetInteraction)}
@ -380,7 +380,7 @@
{#if galleryInView}
<div
class="fixed top-20 z-30 start-1/2 -translate-x-1/2 transition-opacity"
class="fixed top-20 start-1/2 -translate-x-1/2 transition-opacity"
class:opacity-0={!galleryInView}
class:opacity-100={galleryInView}
>

View File

@ -127,7 +127,7 @@
>
<!-- Date group title -->
<div
class="flex z-[100] pt-7 pb-5 max-md:pt-5 max-md:pb-3 h-6 place-items-center text-xs font-medium text-immich-fg dark:text-immich-dark-fg md:text-sm"
class="flex pt-7 pb-5 max-md:pt-5 max-md:pb-3 h-6 place-items-center text-xs font-medium text-immich-fg dark:text-immich-dark-fg md:text-sm"
style:width={dateGroup.width + 'px'}
>
{#if !singleSelect && ((hoveredDateGroup === dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))}

View File

@ -5,6 +5,7 @@
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
import type { Action } from '$lib/components/asset-viewer/actions/action';
import Skeleton from '$lib/components/photos-page/skeleton.svelte';
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
import { albumMapViewManager } from '$lib/managers/album-view-map.manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
@ -26,7 +27,6 @@
import { onMount, type Snippet } from 'svelte';
import type { UpdatePayload } from 'vite';
import Portal from '../shared-components/portal/portal.svelte';
import Scrubber from '../shared-components/scrubber/scrubber.svelte';
import AssetDateGroup from './asset-date-group.svelte';
import DeleteAssetDialog from './delete-asset-dialog.svelte';

View File

@ -44,9 +44,9 @@
onscroll={onScroll}
>
{#if canScrollLeft || canScrollRight}
<div class="sticky start-0 z-20">
<div class="sticky start-0">
{#if canScrollLeft}
<div class="absolute start-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
<div class="absolute start-4 top-[6rem]" transition:fade={{ duration: 200 }}>
<button
type="button"
class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
@ -59,7 +59,7 @@
</div>
{/if}
{#if canScrollRight}
<div class="absolute end-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
<div class="absolute end-4 top-[6rem]" transition:fade={{ duration: 200 }}>
<button
type="button"
class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
@ -85,11 +85,11 @@
alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })}
draggable="false"
/>
<p class="absolute bottom-2 start-4 z-10 text-lg text-white max-md:text-sm">
<p class="absolute bottom-2 start-4 text-lg text-white max-md:text-sm">
{$memoryLaneTitle(memory)}
</p>
<div
class="absolute start-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
class="absolute start-0 top-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
></div>
</a>
{/each}

View File

@ -9,7 +9,7 @@
<div class="overflow-clip" style:height={height + 'px'}>
<div
class="flex z-[100] pt-7 pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-light dark:text-immich-dark-fg md:text-sm"
class="flex pt-7 pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-light dark:text-immich-dark-fg md:text-sm"
>
{title}
</div>

View File

@ -135,7 +135,7 @@
{/if}
<div
class="absolute z-[99] w-full"
class="absolute w-full"
id="suggestion"
bind:this={suggestionContainer}
use:clickOutside={{ onOutclick: () => (hideSuggestion = true) }}

View File

@ -20,16 +20,16 @@
</script>
<script lang="ts">
import { fly } from 'svelte/transition';
import Icon from '$lib/components/elements/icon.svelte';
import { mdiMagnify, mdiUnfoldMoreHorizontal, mdiClose } from '@mdi/js';
import { onMount, tick } from 'svelte';
import type { FormEventHandler } from 'svelte/elements';
import { shortcuts } from '$lib/actions/shortcut';
import { focusOutside } from '$lib/actions/focus-outside';
import { generateId } from '$lib/utils/generate-id';
import { shortcuts } from '$lib/actions/shortcut';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { generateId } from '$lib/utils/generate-id';
import { mdiClose, mdiMagnify, mdiUnfoldMoreHorizontal } from '@mdi/js';
import { onMount, tick } from 'svelte';
import { t } from 'svelte-i18n';
import type { FormEventHandler } from 'svelte/elements';
import { fly } from 'svelte/transition';
interface Props {
label: string;
@ -341,7 +341,7 @@
role="listbox"
id={listboxId}
transition:fly={{ duration: 250 }}
class="fixed text-start text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900 z-[10000]"
class="fixed z-[1] text-start text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900"
class:rounded-b-xl={dropdownDirection === 'bottom'}
class:rounded-t-xl={dropdownDirection === 'top'}
class:shadow={dropdownDirection === 'bottom'}

View File

@ -59,7 +59,7 @@
<div
bind:clientHeight={height}
class="fixed z-10 min-w-[200px] w-max max-w-[300px] overflow-hidden rounded-lg shadow-lg"
class="fixed min-w-[200px] w-max max-w-[300px] overflow-hidden rounded-lg shadow-lg"
style:left="{left}px"
style:top="{top}px"
transition:slide={{ duration: 250, easing: quintOut }}

View File

@ -1,10 +1,10 @@
<script lang="ts">
import { tick, type Snippet } from 'svelte';
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import { shortcuts } from '$lib/actions/shortcut';
import { generateId } from '$lib/utils/generate-id';
import { contextMenuNavigation } from '$lib/actions/context-menu-navigation';
import { shortcuts } from '$lib/actions/shortcut';
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
import { generateId } from '$lib/utils/generate-id';
import { tick, type Snippet } from 'svelte';
interface Props {
title: string;
@ -91,7 +91,7 @@
},
]}
>
<section class="fixed start-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
<section class="fixed start-0 top-0 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
<ContextMenu
{direction}
{x}

View File

@ -66,7 +66,7 @@
let buttonClass = $derived(forceDark ? 'hover:text-immich-dark-gray' : undefined);
</script>
<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full z-[100] bg-transparent">
<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full bg-transparent z-[1]">
<nav
id="asset-selection-app-bar"
class={[

View File

@ -161,7 +161,7 @@
{#if dragStartTarget}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="fixed inset-0 z-[1000] flex h-full w-full flex-col items-center justify-center bg-gray-100/90 text-immich-dark-gray dark:bg-immich-dark-bg/90 dark:text-immich-gray"
class="fixed inset-0 flex h-full w-full flex-col items-center justify-center bg-gray-100/90 text-immich-dark-gray dark:bg-immich-dark-bg/90 dark:text-immich-gray"
transition:fade={{ duration: 250 }}
ondragover={onDragOver}
>

View File

@ -1,10 +1,10 @@
<script lang="ts">
import { clickOutside } from '$lib/actions/click-outside';
import { focusTrap } from '$lib/actions/focus-trap';
import { fade } from 'svelte/transition';
import ModalHeader from '$lib/components/shared-components/modal-header.svelte';
import { generateId } from '$lib/utils/generate-id';
import type { Snippet } from 'svelte';
import { fade } from 'svelte/transition';
interface Props {
onClose: () => void;
@ -77,14 +77,14 @@
role="presentation"
in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }}
class="fixed start-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
class="fixed start-0 top-0 flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
onkeydown={(event) => {
event.stopPropagation();
}}
use:focusTrap
>
<div
class="flex flex-col max-h-[min(95dvh,60rem)] z-[9999] max-w-[95vw] {modalWidth} overflow-hidden rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg pt-3 pb-4"
class="flex flex-col max-h-[min(95dvh,60rem)] max-w-[95vw] {modalWidth} overflow-hidden rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg pt-3 pb-4"
use:clickOutside={{ onOutclick: onClose, onEscape: onClose }}
tabindex="-1"
aria-modal="true"

View File

@ -25,7 +25,7 @@
in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }}
id="account-info-panel"
class="absolute end-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
class="absolute z-[1] end-[25px] top-[75px] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
use:focusTrap
>
<div
@ -33,7 +33,7 @@
>
<div class="relative">
<UserAvatar user={$user} size="xl" />
<div class="absolute z-10 bottom-0 end-0 rounded-full w-6 h-6">
<div class="absolute bottom-0 end-0 rounded-full w-6 h-6">
<CircleIconButton
color="primary"
icon={mdiPencil}

View File

@ -51,7 +51,7 @@
<nav
id="dashboard-navbar"
class="z-auto max-md:h-[var(--navbar-height-md)] h-[var(--navbar-height)] w-dvw text-sm overflow-hidden"
class="max-md:h-[var(--navbar-height-md)] h-[var(--navbar-height)] w-dvw text-sm overflow-hidden"
>
<SkipLink text={$t('skip_to_content')} />
<div

View File

@ -12,8 +12,8 @@
import { Button, Scrollable, Stack, Text } from '@immich/ui';
import { mdiBellOutline, mdiCheckAll } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
const noUnreadNotifications = $derived(notificationManager.notifications.length === 0);
@ -39,7 +39,7 @@
in:fade={{ duration: 100 }}
out:fade={{ duration: 100 }}
id="notification-panel"
class="absolute right-[25px] top-[70px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-100 border border-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray text-light"
class="absolute right-[25px] top-[70px] z-[1] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-100 border border-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray text-light"
use:focusTrap
>
<Stack class="max-h-[500px]">

View File

@ -26,7 +26,7 @@
</script>
{#if showing}
<div class="absolute start-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
<div class="absolute start-0 top-0 h-[3px] w-dvw bg-white">
<span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
</div>
{/if}

View File

@ -5,8 +5,8 @@ import { get } from 'svelte/store';
import { NotificationType, notificationController } from '../notification';
import NotificationList from '../notification-list.svelte';
function _getNotificationListElement(sut: RenderResult<NotificationList>): HTMLAnchorElement | null {
return sut.container.querySelector('#notification-list');
function _getNotificationListElement(): HTMLAnchorElement | null {
return document.body.querySelector('#notification-list');
}
describe('NotificationList component', () => {
@ -23,7 +23,7 @@ describe('NotificationList component', () => {
const status = await sut.findAllByRole('status');
expect(status).toHaveLength(1);
expect(_getNotificationListElement(sut)).not.toBeInTheDocument();
expect(_getNotificationListElement()).not.toBeInTheDocument();
notificationController.show({
message: 'Notification',
@ -31,11 +31,11 @@ describe('NotificationList component', () => {
timeout: 1,
});
await waitFor(() => expect(_getNotificationListElement(sut)).toBeInTheDocument());
await waitFor(() => expect(_getNotificationListElement(sut)?.children).toHaveLength(1));
await waitFor(() => expect(_getNotificationListElement()).toBeInTheDocument());
await waitFor(() => expect(_getNotificationListElement()?.children).toHaveLength(1));
expect(get(notificationController.notificationList)).toHaveLength(1);
await waitFor(() => expect(_getNotificationListElement(sut)).not.toBeInTheDocument());
await waitFor(() => expect(_getNotificationListElement()).not.toBeInTheDocument());
expect(get(notificationController.notificationList)).toHaveLength(0);
});
});

View File

@ -75,7 +75,7 @@
transition:fade={{ duration: 250 }}
style:background-color={backgroundColor[notification.type]}
style:border-color={borderColor[notification.type]}
class="border z-[999999] mb-4 min-h-[80px] w-[300px] rounded-2xl p-4 shadow-md {hoverStyle}"
class="border mb-4 min-h-[80px] w-[300px] rounded-2xl p-4 shadow-md {hoverStyle}"
onclick={handleClick}
onkeydown={handleClick}
>

View File

@ -1,22 +1,25 @@
<script lang="ts">
import { notificationController } from './notification';
import { fade } from 'svelte/transition';
import Portal from '$lib/components/shared-components/portal/portal.svelte';
import { t } from 'svelte-i18n';
import NotificationCard from './notification-card.svelte';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
import { fade } from 'svelte/transition';
import { notificationController } from './notification';
import NotificationCard from './notification-card.svelte';
const { notificationList } = notificationController;
</script>
<div role="status" aria-relevant="additions" aria-label={$t('notifications')}>
{#if $notificationList.length > 0}
<section transition:fade={{ duration: 250 }} id="notification-list" class="fixed end-5 top-[80px] z-[99999999]">
{#each $notificationList as notification (notification.id)}
<div animate:flip={{ duration: 250, easing: quintOut }}>
<NotificationCard {notification} />
</div>
{/each}
</section>
{/if}
</div>
<Portal>
<div role="status" aria-relevant="additions" aria-label={$t('notifications')}>
{#if $notificationList.length > 0}
<section transition:fade={{ duration: 250 }} id="notification-list" class="fixed end-5 top-[80px]">
{#each $notificationList as notification (notification.id)}
<div animate:flip={{ duration: 250, easing: quintOut }}>
<NotificationCard {notification} />
</div>
{/each}
</section>
{/if}
</div>
</Portal>

View File

@ -464,7 +464,7 @@
class={[
{ 'border-b-2': isDragging },
{ 'rounded-bl-md': !isDragging },
'bg-light truncate opacity-85 pointer-events-none absolute end-0 z-[100] min-w-20 max-w-64 w-fit rounded-ss-md border-immich-primary py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg',
'bg-light truncate opacity-85 pointer-events-none absolute end-0 min-w-20 max-w-64 w-fit rounded-ss-md border-immich-primary py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg',
]}
style:top="{hoverY + 2}px"
>
@ -506,7 +506,7 @@
{#if assetStore.scrolling && scrollHoverLabel && !isHover}
<p
transition:fade={{ duration: 200 }}
class="truncate pointer-events-none absolute end-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-subtle/70 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg"
class="truncate pointer-events-none absolute end-0 bottom-0 min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-subtle/70 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg"
>
{scrollHoverLabel}
</p>
@ -514,7 +514,7 @@
</div>
{/if}
<div
class="relative z-10"
class="relative"
style:height={relativeTopOffset + 'px'}
data-id="lead-in"
data-time-segment-bucket-date={segments.at(0)?.date}
@ -535,7 +535,7 @@
>
{#if !usingMobileDevice}
{#if segment.hasLabel}
<div class="absolute end-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
<div class="absolute end-[1.25rem] top-[-16px] text-[12px] dark:text-immich-dark-fg font-immich-mono">
{segment.date.year}
</div>
{/if}
@ -547,6 +547,3 @@
{/each}
<div data-id="lead-out" class="relative" style:height={relativeBottomOffset + 'px'}></div>
</div>
<style>
</style>

View File

@ -282,7 +282,7 @@
class:end-28={isFocus && value.length > 0}
>
<p
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs z-10"
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs"
>
{getSearchTypeText()}
</p>

View File

@ -119,7 +119,7 @@
{#if showMessage}
<dialog
open
class="hidden sidebar:block w-[500px] absolute bottom-[75px] start-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
class="hidden sidebar:block w-[500px] absolute bottom-[75px] start-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl shadow-2xl px-8 py-6"
transition:fade={{ duration: 150 }}
onmouseover={() => (hoverMessage = true)}
onmouseleave={() => (hoverMessage = false)}

View File

@ -37,7 +37,7 @@
<div class="relative">
{#if hasDropdown}
<span class="hidden md:block absolute start-1 z-50 h-full">
<span class="hidden md:block absolute start-1 h-full">
<button
type="button"
aria-label={$t('recent-albums')}

View File

@ -35,7 +35,7 @@
id="sidebar"
aria-label={ariaLabel}
tabindex="-1"
class="immich-scrollbar relative z-auto w-0 sidebar:w-[16rem] overflow-y-auto overflow-x-hidden pt-8 transition-all duration-200"
class="immich-scrollbar relative z-[1] w-0 sidebar:w-[16rem] overflow-y-auto overflow-x-hidden pt-8 transition-all duration-200"
class:shadow-2xl={isExpanded}
class:dark:border-e-immich-dark-gray={isExpanded}
class:border-r={isExpanded}

View File

@ -1,15 +1,15 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store';
import { uploadAssetsStore } from '$lib/stores/upload';
import { uploadExecutionQueue } from '$lib/utils/file-uploader';
import { mdiCancel, mdiCloudUploadOutline, mdiCog, mdiWindowMinimize } from '@mdi/js';
import { t } from 'svelte-i18n';
import { quartInOut } from 'svelte/easing';
import { fade, scale } from 'svelte/transition';
import { uploadAssetsStore } from '$lib/stores/upload';
import Icon from '$lib/components/elements/icon.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { notificationController, NotificationType } from './notification/notification';
import UploadAssetPreview from './upload-asset-preview.svelte';
import { uploadExecutionQueue } from '$lib/utils/file-uploader';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import { mdiCog, mdiWindowMinimize, mdiCancel, mdiCloudUploadOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { locale } from '$lib/stores/preferences.store';
let showDetail = $state(false);
let showOptions = $state(false);
@ -48,7 +48,7 @@
}
uploadAssetsStore.reset();
}}
class="fixed bottom-6 end-16 z-[10000]"
class="fixed bottom-6 end-16"
>
{#if showDetail}
<div

View File

@ -1,37 +1,37 @@
<script lang="ts">
import { afterNavigate, goto, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte';
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
import TreeItemThumbnails from '$lib/components/shared-components/tree/tree-item-thumbnails.svelte';
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
import { AppRoute, QueryParameter } from '$lib/constants';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import type { Viewport } from '$lib/stores/assets-store.svelte';
import { foldersStore } from '$lib/stores/folders.svelte';
import { preferences } from '$lib/stores/user.store';
import { cancelMultiselect } from '$lib/utils/asset-utils';
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte';
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import { preferences } from '$lib/stores/user.store';
import { cancelMultiselect } from '$lib/utils/asset-utils';
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
interface Props {
data: PageData;
@ -87,7 +87,7 @@
</script>
{#if assetInteraction.selectionActive}
<div class="fixed z-[910] top-0 start-0 w-full">
<div class="fixed top-0 start-0 w-full">
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => cancelMultiselect(assetInteraction)}

View File

@ -458,7 +458,7 @@
<dialog
open
transition:fly={{ y: innerHeight, duration: 150, easing: quintOut, opacity: 0 }}
class="absolute start-0 top-0 z-[9999] h-full w-full bg-light"
class="absolute start-0 top-0 h-full w-full bg-light"
aria-modal="true"
aria-labelledby="manage-visibility-title"
use:focusTrap

View File

@ -401,91 +401,6 @@
<MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} />
{/if}
<header>
{#if assetInteraction.selectionActive}
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => assetInteraction.clearMultiselect()}
>
<CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteraction} />
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum />
<AddToAlbum shared />
</ButtonContextMenu>
<FavoriteAction
removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) =>
assetStore.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
/>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem filename="{person.name || 'immich'}.zip" />
<MenuOption
icon={mdiAccountMultipleCheckOutline}
text={$t('fix_incorrect_match')}
onClick={handleReassignAssets}
/>
<ChangeDate menuItem />
<ChangeLocation menuItem />
<ArchiveAction
menuItem
unarchive={assetInteraction.isAllArchived}
onArchive={(assetIds) => assetStore.removeAssets(assetIds)}
/>
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
<TagAction menuItem />
{/if}
<DeleteAssets menuItem onAssetDelete={(assetIds) => handleDeleteAssets(assetIds)} />
</ButtonContextMenu>
</AssetSelectControlBar>
{:else}
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE || viewMode === PersonPageViewMode.BIRTH_DATE}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(previousRoute)}>
{#snippet trailing()}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<MenuOption
text={$t('select_featured_photo')}
icon={mdiAccountBoxOutline}
onClick={() => (viewMode = PersonPageViewMode.SELECT_PERSON)}
/>
<MenuOption
text={person.isHidden ? $t('unhide_person') : $t('hide_person')}
icon={person.isHidden ? mdiEyeOutline : mdiEyeOffOutline}
onClick={() => toggleHidePerson()}
/>
<MenuOption
text={$t('set_date_of_birth')}
icon={mdiCalendarEditOutline}
onClick={() => (viewMode = PersonPageViewMode.BIRTH_DATE)}
/>
<MenuOption
text={$t('merge_people')}
icon={mdiAccountMultipleCheckOutline}
onClick={() => (viewMode = PersonPageViewMode.MERGE_PEOPLE)}
/>
<MenuOption
icon={person.isFavorite ? mdiHeartMinusOutline : mdiHeartOutline}
text={person.isFavorite ? $t('unfavorite') : $t('to_favorite')}
onClick={handleToggleFavorite}
/>
</ButtonContextMenu>
{/snippet}
</ControlAppBar>
{/if}
{#if viewMode === PersonPageViewMode.SELECT_PERSON}
<ControlAppBar onClose={() => (viewMode = PersonPageViewMode.VIEW_ASSETS)}>
{#snippet leading()}
{$t('select_featured_photo')}
{/snippet}
</ControlAppBar>
{/if}
{/if}
</header>
<main
class="relative h-dvh overflow-hidden tall:ms-4 md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)]"
use:scrollMemoryClearer={{
@ -571,7 +486,7 @@
{/if}
</section>
{#if isEditingName}
<div class="absolute z-[999] w-64 sm:w-96">
<div class="absolute w-64 sm:w-96">
{#if isSearchingPeople}
<div
class="flex border h-14 rounded-b-lg border-gray-400 dark:border-immich-dark-gray place-items-center bg-gray-200 p-2 dark:bg-gray-700"
@ -611,3 +526,88 @@
</AssetGrid>
{/key}
</main>
<header>
{#if assetInteraction.selectionActive}
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => assetInteraction.clearMultiselect()}
>
<CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteraction} />
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
<AddToAlbum />
<AddToAlbum shared />
</ButtonContextMenu>
<FavoriteAction
removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) =>
assetStore.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
/>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem filename="{person.name || 'immich'}.zip" />
<MenuOption
icon={mdiAccountMultipleCheckOutline}
text={$t('fix_incorrect_match')}
onClick={handleReassignAssets}
/>
<ChangeDate menuItem />
<ChangeLocation menuItem />
<ArchiveAction
menuItem
unarchive={assetInteraction.isAllArchived}
onArchive={(assetIds) => assetStore.removeAssets(assetIds)}
/>
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
<TagAction menuItem />
{/if}
<DeleteAssets menuItem onAssetDelete={(assetIds) => handleDeleteAssets(assetIds)} />
</ButtonContextMenu>
</AssetSelectControlBar>
{:else}
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE || viewMode === PersonPageViewMode.BIRTH_DATE}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(previousRoute)}>
{#snippet trailing()}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<MenuOption
text={$t('select_featured_photo')}
icon={mdiAccountBoxOutline}
onClick={() => (viewMode = PersonPageViewMode.SELECT_PERSON)}
/>
<MenuOption
text={person.isHidden ? $t('unhide_person') : $t('hide_person')}
icon={person.isHidden ? mdiEyeOutline : mdiEyeOffOutline}
onClick={() => toggleHidePerson()}
/>
<MenuOption
text={$t('set_date_of_birth')}
icon={mdiCalendarEditOutline}
onClick={() => (viewMode = PersonPageViewMode.BIRTH_DATE)}
/>
<MenuOption
text={$t('merge_people')}
icon={mdiAccountMultipleCheckOutline}
onClick={() => (viewMode = PersonPageViewMode.MERGE_PEOPLE)}
/>
<MenuOption
icon={person.isFavorite ? mdiHeartMinusOutline : mdiHeartOutline}
text={person.isFavorite ? $t('unfavorite') : $t('to_favorite')}
onClick={handleToggleFavorite}
/>
</ButtonContextMenu>
{/snippet}
</ControlAppBar>
{/if}
{#if viewMode === PersonPageViewMode.SELECT_PERSON}
<ControlAppBar onClose={() => (viewMode = PersonPageViewMode.VIEW_ASSETS)}>
{#snippet leading()}
{$t('select_featured_photo')}
{/snippet}
</ControlAppBar>
{/if}
{/if}
</header>

View File

@ -80,6 +80,24 @@
});
</script>
<UserPageLayout hideNavbar={assetInteraction.selectionActive} showUploadButton scrollbar={false}>
<AssetGrid
enableRouting={true}
{assetStore}
{assetInteraction}
removeAction={AssetAction.ARCHIVE}
onEscape={handleEscape}
withStacked
>
{#if $preferences.memories.enabled}
<MemoryLane />
{/if}
{#snippet empty()}
<EmptyPlaceholder text={$t('no_assets_message')} onClick={() => openFileUploadDialog()} />
{/snippet}
</AssetGrid>
</UserPageLayout>
{#if assetInteraction.selectionActive}
<AssetSelectControlBar
ownerId={$user.id}
@ -129,21 +147,3 @@
</ButtonContextMenu>
</AssetSelectControlBar>
{/if}
<UserPageLayout hideNavbar={assetInteraction.selectionActive} showUploadButton scrollbar={false}>
<AssetGrid
enableRouting={true}
{assetStore}
{assetInteraction}
removeAction={AssetAction.ARCHIVE}
onEscape={handleEscape}
withStacked
>
{#if $preferences.memories.enabled}
<MemoryLane />
{/if}
{#snippet empty()}
<EmptyPlaceholder text={$t('no_assets_message')} onClick={() => openFileUploadDialog()} />
{/snippet}
</AssetGrid>
</UserPageLayout>

View File

@ -251,7 +251,7 @@
<section>
{#if assetInteraction.selectionActive}
<div class="fixed z-[100] top-0 start-0 w-full">
<div class="fixed top-0 start-0 w-full">
<AssetSelectControlBar
assets={assetInteraction.selectedAssets}
clearSelect={() => cancelMultiselect(assetInteraction)}
@ -289,9 +289,9 @@
</AssetSelectControlBar>
</div>
{:else}
<div class="fixed z-[100] top-0 start-0 w-full">
<div class="fixed top-0 start-0 w-full">
<ControlAppBar onClose={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
<div class="-z-[1] bg-light" style="position:absolute;top:0;left:0;right:0;bottom:0;"></div>
<div class="absolute bg-light"></div>
<div class="w-full flex-1 ps-4">
<SearchBar grayTheme={false} value={terms?.query ?? ''} searchQuery={terms} />
</div>

View File

@ -58,17 +58,6 @@
<meta name="description" content={description} />
</svelte:head>
{#if passwordRequired}
<header>
<ControlAppBar showBackButton={false}>
{#snippet leading()}
<ImmichLogoSmallLink />
{/snippet}
{#snippet trailing()}
<ThemeButton />
{/snippet}
</ControlAppBar>
</header>
<main
class="relative h-dvh overflow-hidden px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] sm:px-12 md:px-24 lg:px-40"
>
@ -85,6 +74,17 @@
</div>
</div>
</main>
<header>
<ControlAppBar showBackButton={false}>
{#snippet leading()}
<ImmichLogoSmallLink />
{/snippet}
{#snippet trailing()}
<ThemeButton />
{/snippet}
</ControlAppBar>
</header>
{/if}
{#if !passwordRequired && sharedLink?.type == SharedLinkType.Album}