mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
refactor: asset media endpoints (#9831)
* refactor: asset media endpoints * refactor: mobile upload livePhoto as separate request * refactor: change mobile backup flow to use new asset upload endpoints * chore: format and analyze dart code * feat: mark motion as hidden when linked * feat: upload video portion of live photo before image portion * fix: incorrect assetApi calls in mobile code * fix: download asset --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Zack Pollard <zackpollard@ymail.com>
This commit is contained in:
parent
66fced40e7
commit
69d2fcb43e
91 changed files with 1932 additions and 2456 deletions
|
|
@ -44,7 +44,7 @@ describe('AlbumCard component', () => {
|
|||
await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
|
||||
|
||||
expect(albumImgElement).toHaveAttribute('alt', album.albumName);
|
||||
expect(sdkMock.getAssetThumbnail).not.toHaveBeenCalled();
|
||||
expect(sdkMock.viewAsset).not.toHaveBeenCalled();
|
||||
|
||||
expect(albumNameElement).toHaveTextContent(album.albumName);
|
||||
expect(albumDetailsElement).toHaveTextContent(new RegExp(detailsText));
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { ThumbnailFormat, type AlbumResponseDto } from '@immich/sdk';
|
||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||
import { type AlbumResponseDto } from '@immich/sdk';
|
||||
|
||||
export let album: AlbumResponseDto | undefined;
|
||||
export let preload = false;
|
||||
export let css = '';
|
||||
|
||||
$: thumbnailUrl =
|
||||
album && album.albumThumbnailAssetId
|
||||
? getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
|
||||
: null;
|
||||
album && album.albumThumbnailAssetId ? getAssetThumbnailUrl({ id: album.albumThumbnailAssetId }) : null;
|
||||
</script>
|
||||
|
||||
<div class="relative aspect-square">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||
import {
|
||||
ReactionType,
|
||||
ThumbnailFormat,
|
||||
createActivity,
|
||||
deleteActivity,
|
||||
getActivities,
|
||||
|
|
@ -182,7 +181,7 @@
|
|||
<a class="aspect-square w-[75px] h-[75px]" href="{AppRoute.ALBUMS}/{albumId}/photos/{reaction.assetId}">
|
||||
<img
|
||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||
src={getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||
src={getAssetThumbnailUrl(reaction.assetId)}
|
||||
alt="Profile picture of {reaction.user.name}, who commented on this asset"
|
||||
/>
|
||||
</a>
|
||||
|
|
@ -235,7 +234,7 @@
|
|||
>
|
||||
<img
|
||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||
src={getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||
src={getAssetThumbnailUrl(reaction.assetId)}
|
||||
alt="Profile picture of {reaction.user.name}, who liked this asset"
|
||||
/>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||
import { ThumbnailFormat, type AlbumResponseDto } from '@immich/sdk';
|
||||
import { type AlbumResponseDto } from '@immich/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { normalizeSearchString } from '$lib/utils/string-utils.js';
|
||||
import AlbumListItemDetails from './album-list-item-details.svelte';
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<span class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
|
||||
{#if album.albumThumbnailAssetId}
|
||||
<img
|
||||
src={getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
|
||||
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"
|
||||
data-testid="album-image"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError, isSharedLink } from '$lib/utils';
|
||||
import { delay, isFlipped } from '$lib/utils/asset-utils';
|
||||
import {
|
||||
ThumbnailFormat,
|
||||
AssetMediaSize,
|
||||
getAssetInfo,
|
||||
updateAsset,
|
||||
type AlbumResponseDto,
|
||||
|
|
@ -474,7 +474,7 @@
|
|||
alt={album.albumName}
|
||||
class="h-[50px] w-[50px] rounded object-cover"
|
||||
src={album.albumThumbnailAssetId &&
|
||||
getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
|
||||
getAssetThumbnailUrl({ id: album.albumThumbnailAssetId, size: AssetMediaSize.Preview })}
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { serveFile, type AssetResponseDto, AssetTypeEnum } from '@immich/sdk';
|
||||
import { getAssetOriginalUrl, getKey } from '$lib/utils';
|
||||
import { AssetMediaSize, AssetTypeEnum, viewAsset, type AssetResponseDto } from '@immich/sdk';
|
||||
import type { AdapterConstructor, PluginConstructor } from '@photo-sphere-viewer/core';
|
||||
import { fade } from 'svelte/transition';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
import { getAssetFileUrl, getKey } from '$lib/utils';
|
||||
import type { AdapterConstructor, PluginConstructor } from '@photo-sphere-viewer/core';
|
||||
export let asset: Pick<AssetResponseDto, 'id' | 'type'>;
|
||||
|
||||
const photoSphereConfigs =
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
|
||||
const loadAssetData = async () => {
|
||||
if (asset.type === AssetTypeEnum.Video) {
|
||||
return { source: getAssetFileUrl(asset.id, false, false) };
|
||||
return { source: getAssetOriginalUrl(asset.id) };
|
||||
}
|
||||
const data = await serveFile({ id: asset.id, isWeb: false, isThumb: false, key: getKey() });
|
||||
const data = await viewAsset({ id: asset.id, size: AssetMediaSize.Preview, key: getKey() });
|
||||
const url = URL.createObjectURL(data);
|
||||
return url;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ describe('PhotoViewer component', () => {
|
|||
|
||||
expect(downloadRequestMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
url: `/api/asset/file/${asset.id}?isThumb=false&isWeb=true&c=${asset.checksum}`,
|
||||
url: `/api/assets/${asset.id}/thumbnail?size=preview&c=${asset.checksum}`,
|
||||
}),
|
||||
);
|
||||
await waitFor(() => expect(screen.getByRole('img')).toBeInTheDocument());
|
||||
|
|
@ -61,7 +61,7 @@ describe('PhotoViewer component', () => {
|
|||
await waitFor(() => expect(screen.getByRole('img')).toHaveAttribute('src', 'url-two'));
|
||||
expect(downloadRequestMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
url: `/api/asset/file/${asset.id}?isThumb=false&isWeb=false&c=${asset.checksum}`,
|
||||
url: `/api/assets/${asset.id}/original?c=${asset.checksum}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
@ -76,7 +76,7 @@ describe('PhotoViewer component', () => {
|
|||
await waitFor(() => expect(screen.getByRole('img')).toHaveAttribute('src', 'url-two'));
|
||||
expect(downloadRequestMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
url: `/api/asset/file/${asset.id}?isThumb=false&isWeb=true&c=new-checksum`,
|
||||
url: `/api/assets/${asset.id}/thumbnail?size=preview&c=new-checksum`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||
import { downloadRequest, getAssetFileUrl, handlePromiseError } from '$lib/utils';
|
||||
import { downloadRequest, getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { type AssetResponseDto, AssetTypeEnum } from '@immich/sdk';
|
||||
import { type AssetResponseDto, AssetTypeEnum, AssetMediaSize } from '@immich/sdk';
|
||||
import { useZoomImageWheel } from '@zoom-image/svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
|
@ -62,7 +62,9 @@
|
|||
|
||||
// TODO: Use sdk once it supports signals
|
||||
const res = await downloadRequest({
|
||||
url: getAssetFileUrl(asset.id, !loadOriginal, false, checksum),
|
||||
url: loadOriginal
|
||||
? getAssetOriginalUrl({ id: asset.id, checksum })
|
||||
: getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Preview, checksum }),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
|
|
@ -76,7 +78,9 @@
|
|||
for (const preloadAsset of preloadAssets) {
|
||||
if (preloadAsset.type === AssetTypeEnum.Image) {
|
||||
await downloadRequest({
|
||||
url: getAssetFileUrl(preloadAsset.id, !loadOriginal, false),
|
||||
url: loadOriginal
|
||||
? getAssetOriginalUrl(preloadAsset.id)
|
||||
: getAssetThumbnailUrl({ id: preloadAsset.id, size: AssetMediaSize.Preview }),
|
||||
signal: abortController.signal,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { loopVideo as loopVideoPreference, videoViewerVolume, videoViewerMuted } from '$lib/stores/preferences.store';
|
||||
import { getAssetFileUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||
import { loopVideo as loopVideoPreference, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
|
||||
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { ThumbnailFormat } from '@immich/sdk';
|
||||
import { AssetMediaSize } from '@immich/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
|
||||
export let assetId: string;
|
||||
export let loopVideo: boolean;
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
let assetFileUrl: string;
|
||||
|
||||
$: {
|
||||
const next = getAssetFileUrl(assetId, false, true, checksum);
|
||||
const next = getAssetPlaybackUrl({ id: assetId, checksum });
|
||||
if (assetFileUrl !== next) {
|
||||
assetFileUrl = next;
|
||||
element && element.load();
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
on:ended={() => dispatch('onVideoEnded')}
|
||||
bind:muted={$videoViewerMuted}
|
||||
bind:volume={$videoViewerVolume}
|
||||
poster={getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg, checksum)}
|
||||
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, checksum })}
|
||||
>
|
||||
<source src={assetFileUrl} type="video/mp4" />
|
||||
<track kind="captions" />
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { ProjectionType } from '$lib/constants';
|
||||
import { getAssetFileUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||
import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { timeToSeconds } from '$lib/utils/date-time';
|
||||
import { AssetTypeEnum, ThumbnailFormat, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
||||
import { playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||
import { getAssetPlaybackUrl } from '$lib/utils';
|
||||
import {
|
||||
mdiArchiveArrowDownOutline,
|
||||
mdiCameraBurst,
|
||||
|
|
@ -33,7 +34,6 @@
|
|||
export let thumbnailSize: number | undefined = undefined;
|
||||
export let thumbnailWidth: number | undefined = undefined;
|
||||
export let thumbnailHeight: number | undefined = undefined;
|
||||
export let format: ThumbnailFormat = ThumbnailFormat.Webp;
|
||||
export let selected = false;
|
||||
export let selectionCandidate = false;
|
||||
export let disabled = false;
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
|
||||
{#if asset.resized}
|
||||
<ImageThumbnail
|
||||
url={getAssetThumbnailUrl(asset.id, format, asset.checksum)}
|
||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, checksum: asset.checksum })}
|
||||
altText={getAltText(asset)}
|
||||
widthStyle="{width}px"
|
||||
heightStyle="{height}px"
|
||||
|
|
@ -197,7 +197,7 @@
|
|||
{#if asset.type === AssetTypeEnum.Video}
|
||||
<div class="absolute top-0 h-full w-full">
|
||||
<VideoThumbnail
|
||||
url={getAssetFileUrl(asset.id, false, true, asset.checksum)}
|
||||
url={getAssetPlaybackUrl({ id: asset.id, checksum: asset.checksum })}
|
||||
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
||||
curve={selected}
|
||||
durationInSeconds={timeToSeconds(asset.duration)}
|
||||
|
|
@ -209,7 +209,7 @@
|
|||
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
||||
<div class="absolute top-0 h-full w-full">
|
||||
<VideoThumbnail
|
||||
url={getAssetFileUrl(asset.livePhotoVideoId, false, true, asset.checksum)}
|
||||
url={getAssetPlaybackUrl({ id: asset.livePhotoVideoId, checksum: asset.checksum })}
|
||||
pauseIcon={mdiMotionPauseOutline}
|
||||
playIcon={mdiMotionPlayOutline}
|
||||
showTime={false}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { photoViewer } from '$lib/stores/assets.store';
|
||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
|
||||
import { AssetTypeEnum, ThumbnailFormat, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { linear } from 'svelte/easing';
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
if (assetType === AssetTypeEnum.Image) {
|
||||
image = $photoViewer;
|
||||
} else if (assetType === AssetTypeEnum.Video) {
|
||||
const data = getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
|
||||
const data = getAssetThumbnailUrl(assetId);
|
||||
const img: HTMLImageElement = new Image();
|
||||
img.src = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.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 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';
|
||||
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 AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.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 ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||
import { type Viewport } from '$lib/stores/assets.store';
|
||||
import { memoryStore } from '$lib/stores/memory.store';
|
||||
import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||
import { ThumbnailFormat, getMemoryLane, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetMediaSize, getMemoryLane, type AssetResponseDto } from '@immich/sdk';
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiChevronLeft,
|
||||
|
|
@ -243,7 +243,7 @@
|
|||
{#if previousMemory}
|
||||
<img
|
||||
class="h-full w-full rounded-2xl object-cover"
|
||||
src={getAssetThumbnailUrl(previousMemory.assets[0].id, ThumbnailFormat.Jpeg)}
|
||||
src={getAssetThumbnailUrl({ id: previousMemory.assets[0].id, size: AssetMediaSize.Preview })}
|
||||
alt="Previous memory"
|
||||
draggable="false"
|
||||
/>
|
||||
|
|
@ -275,7 +275,7 @@
|
|||
<img
|
||||
transition:fade
|
||||
class="h-full w-full rounded-2xl object-contain transition-all"
|
||||
src={getAssetThumbnailUrl(currentAsset.id, ThumbnailFormat.Jpeg)}
|
||||
src={getAssetThumbnailUrl({ id: currentAsset.id, size: AssetMediaSize.Preview })}
|
||||
alt={currentAsset.exifInfo?.description}
|
||||
draggable="false"
|
||||
/>
|
||||
|
|
@ -321,7 +321,7 @@
|
|||
{#if nextMemory}
|
||||
<img
|
||||
class="h-full w-full rounded-2xl object-cover"
|
||||
src={getAssetThumbnailUrl(nextMemory.assets[0].id, ThumbnailFormat.Jpeg)}
|
||||
src={getAssetThumbnailUrl({ id: nextMemory.assets[0].id, size: AssetMediaSize.Preview })}
|
||||
alt="Next memory"
|
||||
draggable="false"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { memoryStore } from '$lib/stores/memory.store';
|
||||
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { ThumbnailFormat, getMemoryLane } from '@immich/sdk';
|
||||
import { getMemoryLane } from '@immich/sdk';
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
>
|
||||
<img
|
||||
class="h-full w-full rounded-xl object-cover"
|
||||
src={getAssetThumbnailUrl(memory.assets[0].id, ThumbnailFormat.Webp)}
|
||||
src={getAssetThumbnailUrl(memory.assets[0].id)}
|
||||
alt={`Memory Lane ${getAltText(memory.assets[0])}`}
|
||||
draggable="false"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@
|
|||
/>
|
||||
{:else}
|
||||
<img
|
||||
src={getAssetThumbnailUrl(feature.properties?.id, undefined)}
|
||||
src={getAssetThumbnailUrl(feature.properties?.id)}
|
||||
class="rounded-full w-[60px] h-[60px] border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-cover bg-immich-primary"
|
||||
alt={feature.properties?.city && feature.properties.country
|
||||
? `Map marker for images taken in ${feature.properties.city}, ${feature.properties.country}`
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||
import { ThumbnailFormat, type AssetResponseDto, type DuplicateResponseDto, getAllAlbums } from '@immich/sdk';
|
||||
import { type AssetResponseDto, type DuplicateResponseDto, getAllAlbums } from '@immich/sdk';
|
||||
import { mdiCheck, mdiTrashCanOutline } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { s } from '$lib/utils';
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
<button type="button" on:click={() => onSelectAsset(asset)} class="block relative">
|
||||
<!-- THUMBNAIL-->
|
||||
<img
|
||||
src={getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
|
||||
src={getAssetThumbnailUrl(asset.id)}
|
||||
alt={asset.id}
|
||||
title={`${assetData}`}
|
||||
class={`w-[250px] h-[250px] object-cover rounded-t-xl border-t-[4px] border-l-[4px] border-r-[4px] border-gray-300 ${isSelected ? 'border-immich-primary dark:border-immich-dark-primary' : 'dark:border-gray-800'} transition-all`}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue