Compare commits

...

18 Commits

Author SHA1 Message Date
Norihide Saito
e9a36f0391 fix: update selectors for disabled state in thumbnail component 2025-11-30 11:44:15 +09:00
Norihide Saito
d10dfc05cc Merge remote-tracking branch 'origin/feat/shared-album-owner-labels' into refactor/expectSelectedReadonly 2025-11-30 09:54:20 +09:00
Norihide Saito
0d5685a3fa refactor(e2e/web): resolve todo of expectSelectedReadonly 2025-11-30 09:35:30 +09:00
CJPeckover
4c33fbb5e0 Merge branch 'immich-main' into feat/shared-album-owner-labels 2025-11-25 22:59:30 -05:00
CJPeckover
b687237d8f format 2025-11-23 22:03:16 -05:00
CJPeckover
e75d9f5613 add missing import 2025-11-23 21:52:14 -05:00
CJPeckover
f353c99223 Don't show 'view owners' button if the album doesn't have editors 2025-11-23 21:49:47 -05:00
CJPeckover
c0afde91a7 add @idubnori suggestion for the name font 2025-11-23 21:33:13 -05:00
CJPeckover
ff19cd0107 update new Timeline with albumUsers 2025-11-23 21:01:33 -05:00
CJPeckover
d227077543 Merge branch 'immich-main' into feat/shared-album-owner-labels 2025-11-23 21:01:07 -05:00
CJPeckover
6e0005acfd - add toggle to show/hide asset owner names 2025-08-25 19:55:25 -04:00
CJPeckover
55a196bfa0 Merge branch 'immich-main' into feat/shared-album-owner-labels 2025-08-25 18:27:51 -04:00
CJPeckover
d0b49846dc format 2025-08-23 01:12:37 -04:00
CJPeckover
8896b2dbf5 fix lint 2025-08-23 01:11:10 -04:00
CJPeckover
251e644b2a - cleanup albumUsers creation
- use font-light for the user's name
2025-08-23 01:09:51 -04:00
CJPeckover
a02635f9a5 cleanup 2025-08-23 00:57:50 -04:00
CJPeckover
104f3dfcc3 - change owner to their name in white text instead of the avatar 2025-08-23 00:54:50 -04:00
CJPeckover
b7e3b48a44 - pass available album users along to the thumbnail through the asset-date-group
- show a small user-avatar in bottom right of thumbnail
2025-08-22 17:29:03 -04:00
4 changed files with 42 additions and 9 deletions

View File

@@ -117,12 +117,9 @@ export const thumbnailUtils = {
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
},
async expectSelectedReadonly(page: Page, assetId: string) {
// todo - need a data attribute for selected
await expect(
page.locator(
`[data-thumbnail-focus-container][data-asset="${assetId}"] > .group.cursor-not-allowed > .rounded-xl`,
),
).toBeVisible();
const container = thumbnailUtils.withAssetId(page, assetId);
await expect(container.locator('[data-selected]')).toBeVisible();
await expect(container.locator('[data-disabled]')).toBeVisible();
},
async expectTimelineHasOnScreenAssets(page: Page) {
const first = await thumbnailUtils.getFirstInViewport(page);

View File

@@ -4,7 +4,7 @@
import { getAssetOriginalUrl, 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,
@@ -46,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;
@@ -64,6 +65,7 @@
readonly = false,
showArchiveIcon = false,
showStackedIcon = true,
albumUsers = [],
onClick = undefined,
onSelect = undefined,
onMouseEvent = undefined,
@@ -85,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();
@@ -220,6 +224,8 @@
bind:this={element}
data-asset={asset.id}
data-thumbnail-focus-container
data-selected={selected || undefined}
data-disabled={disabled || undefined}
tabindex={0}
role="link"
>
@@ -268,6 +274,14 @@
</div>
{/if}
{#if !!assetOwner}
<div class="absolute bottom-1 end-2 max-w-[50%]">
<p class="text-xs font-medium text-white drop-shadow-lg max-w-[100%] truncate">
{assetOwner.name}
</p>
</div>
{/if}
{#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
<Icon data-icon-archive icon={mdiArchiveArrowDownOutline} size="24" class="text-white" />

View File

@@ -23,7 +23,7 @@
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { isAssetViewerRoute, navigate } from '$lib/utils/navigation';
import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util';
import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import { type AlbumResponseDto, type PersonResponseDto, type UserResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { onDestroy, onMount, type Snippet } from 'svelte';
import type { UpdatePayload } from 'vite';
@@ -49,6 +49,7 @@
showArchiveIcon?: boolean;
isShared?: boolean;
album?: AlbumResponseDto | null;
albumUsers?: UserResponseDto[];
person?: PersonResponseDto | null;
isShowDeleteConfirmation?: boolean;
onSelect?: (asset: TimelineAsset) => void;
@@ -81,6 +82,7 @@
showArchiveIcon = false,
isShared = false,
album = null,
albumUsers = [],
person = null,
isShowDeleteConfirmation = $bindable(false),
onSelect = () => {},
@@ -702,6 +704,7 @@
showStackedIcon={withStacked}
{showArchiveIcon}
{asset}
{albumUsers}
{groupIndex}
onClick={(asset) => {
if (typeof onThumbnailClick === 'function') {

View File

@@ -66,6 +66,7 @@
} from '@immich/sdk';
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
import {
mdiAccountEyeOutline,
mdiArrowLeft,
mdiCogOutline,
mdiDeleteOutline,
@@ -100,6 +101,7 @@
let isCreatingSharedAlbum = $state(false);
let isShowActivity = $state(false);
let albumOrder: AssetOrder | undefined = $state(data.album.order);
let showAlbumUsers = $state(false);
const assetInteraction = new AssetInteraction();
const timelineInteraction = new AssetInteraction();
@@ -290,6 +292,11 @@
let album = $derived(data.album);
let albumId = $derived(album.id);
const containsEditors = $derived(album?.shared && album.albumUsers.some(({ role }) => role === AlbumUserRole.Editor));
const albumUsers = $derived(
showAlbumUsers && containsEditors ? [album.owner, ...album.albumUsers.map(({ user }) => user)] : [],
);
$effect(() => {
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
isShowActivity = false;
@@ -418,7 +425,8 @@
<Timeline
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
{album}
bind:timelineManager
{albumUsers}
{timelineManager}
{options}
assetInteraction={currentAssetIntersection}
{isShared}
@@ -597,6 +605,17 @@
{#snippet trailing()}
<CastButton />
{#if containsEditors}
<IconButton
variant="ghost"
shape="round"
color="secondary"
aria-label="view asset owners"
icon={mdiAccountEyeOutline}
onclick={() => (showAlbumUsers = !showAlbumUsers)}
/>
{/if}
{#if isEditor}
<IconButton
variant="ghost"