diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
index e4b590b8ea..8c9f58b239 100644
--- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
@@ -5,7 +5,7 @@
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
import { timeToSeconds } from '$lib/utils/date-time';
import { getAltText } from '$lib/utils/thumbnail-util';
- import { AssetMediaSize, AssetVisibility } from '@immich/sdk';
+ import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk';
import {
mdiArchiveArrowDownOutline,
mdiCameraBurst,
@@ -17,6 +17,7 @@
} from '@mdi/js';
import { thumbhash } from '$lib/actions/thumbhash';
+ import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
@@ -45,6 +46,7 @@
imageClass?: ClassValue;
brokenAssetClass?: ClassValue;
dimmed?: boolean;
+ albumUsers?: UserResponseDto[];
onClick?: (asset: TimelineAsset) => void;
onSelect?: (asset: TimelineAsset) => void;
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
@@ -63,6 +65,7 @@
readonly = false,
showArchiveIcon = false,
showStackedIcon = true,
+ albumUsers = [],
onClick = undefined,
onSelect = undefined,
onMouseEvent = undefined,
@@ -84,6 +87,8 @@
let width = $derived(thumbnailSize || thumbnailWidth || 235);
let height = $derived(thumbnailSize || thumbnailHeight || 235);
+ let assetOwner = $derived(albumUsers?.find((user) => user.id === asset.ownerId) ?? null);
+
const onIconClickedHandler = (e?: MouseEvent) => {
e?.stopPropagation();
e?.preventDefault();
@@ -267,6 +272,12 @@
{/if}
+ {#if !!assetOwner}
+
+
+
+ {/if}
+
{#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
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 6fab96c3ec..61c283e3fe 100644
--- a/web/src/lib/components/photos-page/asset-date-group.svelte
+++ b/web/src/lib/components/photos-page/asset-date-group.svelte
@@ -9,6 +9,7 @@
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { uploadAssetsStore } from '$lib/stores/upload';
import { navigate } from '$lib/utils/navigation';
+ import type { UserResponseDto } from '@immich/sdk';
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
@@ -25,6 +26,7 @@
monthGroup: MonthGroup;
timelineManager: TimelineManager;
assetInteraction: AssetInteraction;
+ albumUsers?: UserResponseDto[];
onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
onSelectAssets: (asset: TimelineAsset) => void;
@@ -40,6 +42,7 @@
monthGroup = $bindable(),
assetInteraction,
timelineManager,
+ albumUsers = [],
onSelect,
onSelectAssets,
onSelectAssetCandidates,
@@ -189,6 +192,7 @@
showStackedIcon={withStacked}
{showArchiveIcon}
{asset}
+ {albumUsers}
{groupIndex}
onClick={(asset) => onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset)}
onSelect={(asset) => assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle)}
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index 69faf8e1e5..d2f44c5257 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -30,7 +30,13 @@
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
import { navigate } from '$lib/utils/navigation';
import { getTimes, toTimelineAsset, type ScrubberListener, type TimelineYearMonth } from '$lib/utils/timeline-util';
- import { AssetVisibility, getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
+ import {
+ AssetVisibility,
+ getAssetInfo,
+ type AlbumResponseDto,
+ type PersonResponseDto,
+ type UserResponseDto,
+ } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { DateTime } from 'luxon';
import { onMount, type Snippet } from 'svelte';
@@ -88,6 +94,15 @@
let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget, mutex } = assetViewingStore;
+ const isUser = (user: UserResponseDto | undefined): user is UserResponseDto => {
+ return !!user;
+ };
+ const albumUsers = $derived(
+ album?.shared && album?.albumUsers.length
+ ? [album?.owner, ...(album?.albumUsers?.map(({ user }) => user) ?? [])].filter((element) => isUser(element))
+ : [],
+ );
+
let element: HTMLElement | undefined = $state();
let timelineElement: HTMLElement | undefined = $state();
@@ -936,6 +951,7 @@
{isSelectionMode}
{singleSelect}
{monthGroup}
+ {albumUsers}
onSelect={({ title, assets }) => handleGroupSelect(timelineManager, title, assets)}
onSelectAssetCandidates={handleSelectAssetCandidates}
onSelectAssets={handleSelectAssets}