mirror of
https://github.com/immich-app/immich.git
synced 2025-12-17 09:13:17 +03:00
Compare commits
18 Commits
0b9a11a0b1
...
refactor/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9a36f0391 | ||
|
|
d10dfc05cc | ||
|
|
0d5685a3fa | ||
|
|
4c33fbb5e0 | ||
|
|
b687237d8f | ||
|
|
e75d9f5613 | ||
|
|
f353c99223 | ||
|
|
c0afde91a7 | ||
|
|
ff19cd0107 | ||
|
|
d227077543 | ||
|
|
6e0005acfd | ||
|
|
55a196bfa0 | ||
|
|
d0b49846dc | ||
|
|
8896b2dbf5 | ||
|
|
251e644b2a | ||
|
|
a02635f9a5 | ||
|
|
104f3dfcc3 | ||
|
|
b7e3b48a44 |
@@ -117,12 +117,9 @@ export const thumbnailUtils = {
|
|||||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
||||||
},
|
},
|
||||||
async expectSelectedReadonly(page: Page, assetId: string) {
|
async expectSelectedReadonly(page: Page, assetId: string) {
|
||||||
// todo - need a data attribute for selected
|
const container = thumbnailUtils.withAssetId(page, assetId);
|
||||||
await expect(
|
await expect(container.locator('[data-selected]')).toBeVisible();
|
||||||
page.locator(
|
await expect(container.locator('[data-disabled]')).toBeVisible();
|
||||||
`[data-thumbnail-focus-container][data-asset="${assetId}"] > .group.cursor-not-allowed > .rounded-xl`,
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
|
||||||
},
|
},
|
||||||
async expectTimelineHasOnScreenAssets(page: Page) {
|
async expectTimelineHasOnScreenAssets(page: Page) {
|
||||||
const first = await thumbnailUtils.getFirstInViewport(page);
|
const first = await thumbnailUtils.getFirstInViewport(page);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { timeToSeconds } from '$lib/utils/date-time';
|
import { timeToSeconds } from '$lib/utils/date-time';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
import { AssetMediaSize, AssetVisibility } from '@immich/sdk';
|
import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
mdiArchiveArrowDownOutline,
|
mdiArchiveArrowDownOutline,
|
||||||
mdiCameraBurst,
|
mdiCameraBurst,
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
imageClass?: ClassValue;
|
imageClass?: ClassValue;
|
||||||
brokenAssetClass?: ClassValue;
|
brokenAssetClass?: ClassValue;
|
||||||
dimmed?: boolean;
|
dimmed?: boolean;
|
||||||
|
albumUsers?: UserResponseDto[];
|
||||||
onClick?: (asset: TimelineAsset) => void;
|
onClick?: (asset: TimelineAsset) => void;
|
||||||
onSelect?: (asset: TimelineAsset) => void;
|
onSelect?: (asset: TimelineAsset) => void;
|
||||||
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
|
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
readonly = false,
|
readonly = false,
|
||||||
showArchiveIcon = false,
|
showArchiveIcon = false,
|
||||||
showStackedIcon = true,
|
showStackedIcon = true,
|
||||||
|
albumUsers = [],
|
||||||
onClick = undefined,
|
onClick = undefined,
|
||||||
onSelect = undefined,
|
onSelect = undefined,
|
||||||
onMouseEvent = undefined,
|
onMouseEvent = undefined,
|
||||||
@@ -85,6 +87,8 @@
|
|||||||
let width = $derived(thumbnailSize || thumbnailWidth || 235);
|
let width = $derived(thumbnailSize || thumbnailWidth || 235);
|
||||||
let height = $derived(thumbnailSize || thumbnailHeight || 235);
|
let height = $derived(thumbnailSize || thumbnailHeight || 235);
|
||||||
|
|
||||||
|
let assetOwner = $derived(albumUsers?.find((user) => user.id === asset.ownerId) ?? null);
|
||||||
|
|
||||||
const onIconClickedHandler = (e?: MouseEvent) => {
|
const onIconClickedHandler = (e?: MouseEvent) => {
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
@@ -220,6 +224,8 @@
|
|||||||
bind:this={element}
|
bind:this={element}
|
||||||
data-asset={asset.id}
|
data-asset={asset.id}
|
||||||
data-thumbnail-focus-container
|
data-thumbnail-focus-container
|
||||||
|
data-selected={selected || undefined}
|
||||||
|
data-disabled={disabled || undefined}
|
||||||
tabindex={0}
|
tabindex={0}
|
||||||
role="link"
|
role="link"
|
||||||
>
|
>
|
||||||
@@ -268,6 +274,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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}
|
{#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
|
||||||
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
|
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
|
||||||
<Icon data-icon-archive icon={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
<Icon data-icon-archive icon={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
|
||||||
import { isAssetViewerRoute, navigate } from '$lib/utils/navigation';
|
import { isAssetViewerRoute, navigate } from '$lib/utils/navigation';
|
||||||
import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util';
|
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 { DateTime } from 'luxon';
|
||||||
import { onDestroy, onMount, type Snippet } from 'svelte';
|
import { onDestroy, onMount, type Snippet } from 'svelte';
|
||||||
import type { UpdatePayload } from 'vite';
|
import type { UpdatePayload } from 'vite';
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
showArchiveIcon?: boolean;
|
showArchiveIcon?: boolean;
|
||||||
isShared?: boolean;
|
isShared?: boolean;
|
||||||
album?: AlbumResponseDto | null;
|
album?: AlbumResponseDto | null;
|
||||||
|
albumUsers?: UserResponseDto[];
|
||||||
person?: PersonResponseDto | null;
|
person?: PersonResponseDto | null;
|
||||||
isShowDeleteConfirmation?: boolean;
|
isShowDeleteConfirmation?: boolean;
|
||||||
onSelect?: (asset: TimelineAsset) => void;
|
onSelect?: (asset: TimelineAsset) => void;
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
showArchiveIcon = false,
|
showArchiveIcon = false,
|
||||||
isShared = false,
|
isShared = false,
|
||||||
album = null,
|
album = null,
|
||||||
|
albumUsers = [],
|
||||||
person = null,
|
person = null,
|
||||||
isShowDeleteConfirmation = $bindable(false),
|
isShowDeleteConfirmation = $bindable(false),
|
||||||
onSelect = () => {},
|
onSelect = () => {},
|
||||||
@@ -702,6 +704,7 @@
|
|||||||
showStackedIcon={withStacked}
|
showStackedIcon={withStacked}
|
||||||
{showArchiveIcon}
|
{showArchiveIcon}
|
||||||
{asset}
|
{asset}
|
||||||
|
{albumUsers}
|
||||||
{groupIndex}
|
{groupIndex}
|
||||||
onClick={(asset) => {
|
onClick={(asset) => {
|
||||||
if (typeof onThumbnailClick === 'function') {
|
if (typeof onThumbnailClick === 'function') {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
|
import { Button, Icon, IconButton, modalManager, toastManager } from '@immich/ui';
|
||||||
import {
|
import {
|
||||||
|
mdiAccountEyeOutline,
|
||||||
mdiArrowLeft,
|
mdiArrowLeft,
|
||||||
mdiCogOutline,
|
mdiCogOutline,
|
||||||
mdiDeleteOutline,
|
mdiDeleteOutline,
|
||||||
@@ -100,6 +101,7 @@
|
|||||||
let isCreatingSharedAlbum = $state(false);
|
let isCreatingSharedAlbum = $state(false);
|
||||||
let isShowActivity = $state(false);
|
let isShowActivity = $state(false);
|
||||||
let albumOrder: AssetOrder | undefined = $state(data.album.order);
|
let albumOrder: AssetOrder | undefined = $state(data.album.order);
|
||||||
|
let showAlbumUsers = $state(false);
|
||||||
|
|
||||||
const assetInteraction = new AssetInteraction();
|
const assetInteraction = new AssetInteraction();
|
||||||
const timelineInteraction = new AssetInteraction();
|
const timelineInteraction = new AssetInteraction();
|
||||||
@@ -290,6 +292,11 @@
|
|||||||
let album = $derived(data.album);
|
let album = $derived(data.album);
|
||||||
let albumId = $derived(album.id);
|
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(() => {
|
$effect(() => {
|
||||||
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
|
if (!album.isActivityEnabled && activityManager.commentCount === 0) {
|
||||||
isShowActivity = false;
|
isShowActivity = false;
|
||||||
@@ -418,7 +425,8 @@
|
|||||||
<Timeline
|
<Timeline
|
||||||
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
|
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
|
||||||
{album}
|
{album}
|
||||||
bind:timelineManager
|
{albumUsers}
|
||||||
|
{timelineManager}
|
||||||
{options}
|
{options}
|
||||||
assetInteraction={currentAssetIntersection}
|
assetInteraction={currentAssetIntersection}
|
||||||
{isShared}
|
{isShared}
|
||||||
@@ -597,6 +605,17 @@
|
|||||||
{#snippet trailing()}
|
{#snippet trailing()}
|
||||||
<CastButton />
|
<CastButton />
|
||||||
|
|
||||||
|
{#if containsEditors}
|
||||||
|
<IconButton
|
||||||
|
variant="ghost"
|
||||||
|
shape="round"
|
||||||
|
color="secondary"
|
||||||
|
aria-label="view asset owners"
|
||||||
|
icon={mdiAccountEyeOutline}
|
||||||
|
onclick={() => (showAlbumUsers = !showAlbumUsers)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if isEditor}
|
{#if isEditor}
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
Reference in New Issue
Block a user