mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(web): lighter timeline buckets (#17719)
* feat(web): lighter timeline buckets * GalleryViewer * weird ssr * Remove generics from AssetInteraction * ensure keys on getAssetInfo, alt-text * empty - trigger ci * re-add alt-text * test fix * update tests * tests * missing import * fix: flappy e2e test * lint * revert settings * unneeded cast * fix after merge * missing import * lint * review * lint * avoid abbreviations * review comment - type safety in test * merge conflicts * lint * lint/abbreviations * fix: left-over migration --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
a65c905621
commit
0bbe70e6a3
53 changed files with 725 additions and 471 deletions
|
|
@ -1,20 +1,21 @@
|
|||
import type { AssetAction } from '$lib/constants';
|
||||
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
|
||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
import type { AlbumResponseDto } from '@immich/sdk';
|
||||
|
||||
type ActionMap = {
|
||||
[AssetAction.ARCHIVE]: { asset: AssetResponseDto };
|
||||
[AssetAction.UNARCHIVE]: { asset: AssetResponseDto };
|
||||
[AssetAction.FAVORITE]: { asset: AssetResponseDto };
|
||||
[AssetAction.UNFAVORITE]: { asset: AssetResponseDto };
|
||||
[AssetAction.TRASH]: { asset: AssetResponseDto };
|
||||
[AssetAction.DELETE]: { asset: AssetResponseDto };
|
||||
[AssetAction.RESTORE]: { asset: AssetResponseDto };
|
||||
[AssetAction.ADD]: { asset: AssetResponseDto };
|
||||
[AssetAction.ADD_TO_ALBUM]: { asset: AssetResponseDto; album: AlbumResponseDto };
|
||||
[AssetAction.UNSTACK]: { assets: AssetResponseDto[] };
|
||||
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: AssetResponseDto };
|
||||
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: AssetResponseDto };
|
||||
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: AssetResponseDto };
|
||||
[AssetAction.ARCHIVE]: { asset: TimelineAsset };
|
||||
[AssetAction.UNARCHIVE]: { asset: TimelineAsset };
|
||||
[AssetAction.FAVORITE]: { asset: TimelineAsset };
|
||||
[AssetAction.UNFAVORITE]: { asset: TimelineAsset };
|
||||
[AssetAction.TRASH]: { asset: TimelineAsset };
|
||||
[AssetAction.DELETE]: { asset: TimelineAsset };
|
||||
[AssetAction.RESTORE]: { asset: TimelineAsset };
|
||||
[AssetAction.ADD]: { asset: TimelineAsset };
|
||||
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto };
|
||||
[AssetAction.UNSTACK]: { assets: TimelineAsset[] };
|
||||
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
|
||||
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
|
||||
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
|
||||
};
|
||||
|
||||
export type Action = {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { addAssetsToAlbum, addAssetsToNewAlbum } from '$lib/utils/asset-utils';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -24,14 +25,14 @@
|
|||
showSelectionModal = false;
|
||||
const album = await addAssetsToNewAlbum(albumName, [asset.id]);
|
||||
if (album) {
|
||||
onAction({ type: AssetAction.ADD_TO_ALBUM, asset, album });
|
||||
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album });
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToAlbum = async (album: AlbumResponseDto) => {
|
||||
showSelectionModal = false;
|
||||
await addAssetsToAlbum(album.id, [asset.id]);
|
||||
onAction({ type: AssetAction.ADD_TO_ALBUM, asset, album });
|
||||
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album });
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { toggleArchive } from '$lib/utils/asset-utils';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -18,11 +19,11 @@
|
|||
|
||||
const onArchive = async () => {
|
||||
if (!asset.isArchived) {
|
||||
preAction({ type: AssetAction.ARCHIVE, asset });
|
||||
preAction({ type: AssetAction.ARCHIVE, asset: toTimelineAsset(asset) });
|
||||
}
|
||||
const updatedAsset = await toggleArchive(asset);
|
||||
if (updatedAsset) {
|
||||
onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset });
|
||||
onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset: toTimelineAsset(asset) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import { showDeleteModal } from '$lib/stores/preferences.store';
|
||||
import { featureFlags } from '$lib/stores/server-config.store';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { deleteAssets, type AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -42,9 +43,9 @@
|
|||
|
||||
const trashAsset = async () => {
|
||||
try {
|
||||
preAction({ type: AssetAction.TRASH, asset });
|
||||
preAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) });
|
||||
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
|
||||
onAction({ type: AssetAction.TRASH, asset });
|
||||
onAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) });
|
||||
|
||||
notificationController.show({
|
||||
message: $t('moved_to_trash'),
|
||||
|
|
@ -58,7 +59,7 @@
|
|||
const deleteAsset = async () => {
|
||||
try {
|
||||
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
|
||||
onAction({ type: AssetAction.DELETE, asset });
|
||||
onAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) });
|
||||
|
||||
notificationController.show({
|
||||
message: $t('permanently_deleted_asset'),
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@
|
|||
import { shortcut } from '$lib/actions/shortcut';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
import { downloadFile } from '$lib/utils/asset-utils';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { getAssetInfo } from '@immich/sdk';
|
||||
import { mdiFolderDownloadOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
asset: TimelineAsset;
|
||||
menuItem?: boolean;
|
||||
}
|
||||
|
||||
let { asset, menuItem = false }: Props = $props();
|
||||
|
||||
const onDownloadFile = () => downloadFile(asset);
|
||||
const onDownloadFile = async () => downloadFile(await getAssetInfo({ id: asset.id, key: authManager.key }));
|
||||
</script>
|
||||
|
||||
<svelte:window use:shortcut={{ shortcut: { key: 'd', shift: true }, onShortcut: onDownloadFile }} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiHeart, mdiHeartOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -30,7 +31,10 @@
|
|||
|
||||
asset = { ...asset, isFavorite: data.isFavorite };
|
||||
|
||||
onAction({ type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE, asset });
|
||||
onAction({
|
||||
type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE,
|
||||
asset: toTimelineAsset(asset),
|
||||
});
|
||||
|
||||
notificationController.show({
|
||||
type: NotificationType.Info,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { AssetAction } from '$lib/constants';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
|
||||
import { mdiPinOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -29,7 +30,7 @@
|
|||
|
||||
const keptAsset = await keepThisDeleteOthers(asset, stack);
|
||||
if (keptAsset) {
|
||||
onAction({ type: AssetAction.UNSTACK, assets: [keptAsset] });
|
||||
onAction({ type: AssetAction.UNSTACK, assets: [toTimelineAsset(keptAsset)] });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { restoreAssets, type AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiHistory } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -23,7 +24,7 @@
|
|||
await restoreAssets({ bulkIdsDto: { ids: [asset.id] } });
|
||||
asset.isTrashed = false;
|
||||
|
||||
onAction({ type: AssetAction.RESTORE, asset });
|
||||
onAction({ type: AssetAction.RESTORE, asset: toTimelineAsset(asset) });
|
||||
|
||||
notificationController.show({
|
||||
type: NotificationType.Info,
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { AssetVisibility, updateAssets, Visibility, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetVisibility, updateAssets, Visibility } from '@immich/sdk';
|
||||
import { mdiEyeOffOutline, mdiFolderMoveOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { OnAction, PreAction } from './action';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
asset: TimelineAsset;
|
||||
onAction: OnAction;
|
||||
preAction: PreAction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { deleteStack } from '$lib/utils/asset-utils';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { StackResponseDto } from '@immich/sdk';
|
||||
import { mdiImageMinusOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
const handleUnstack = async () => {
|
||||
const unstackedAssets = await deleteStack([stack.id]);
|
||||
if (unstackedAssets) {
|
||||
onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets });
|
||||
onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets.map((asset) => toTimelineAsset(asset)) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ describe('AssetViewerNavBar component', () => {
|
|||
showDownloadButton: false,
|
||||
showMotionPlayButton: false,
|
||||
showShareButton: false,
|
||||
preAction: () => {},
|
||||
onZoomImage: () => {},
|
||||
onCopyImage: () => {},
|
||||
onAction: () => {},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
import { getAssetJobName, getSharedLink } from '$lib/utils';
|
||||
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import {
|
||||
AssetJobName,
|
||||
AssetTypeEnum,
|
||||
|
|
@ -138,7 +139,7 @@
|
|||
{/if}
|
||||
|
||||
{#if !isOwner && showDownloadButton}
|
||||
<DownloadAction {asset} />
|
||||
<DownloadAction asset={toTimelineAsset(asset)} />
|
||||
{/if}
|
||||
|
||||
{#if showDetailButton}
|
||||
|
|
@ -166,7 +167,7 @@
|
|||
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
|
||||
{/if}
|
||||
{#if showDownloadButton}
|
||||
<DownloadAction {asset} menuItem />
|
||||
<DownloadAction asset={toTimelineAsset(asset)} menuItem />
|
||||
{/if}
|
||||
|
||||
{#if !isLocked}
|
||||
|
|
@ -210,7 +211,7 @@
|
|||
{/if}
|
||||
|
||||
{#if !asset.isTrashed}
|
||||
<SetVisibilityAction {asset} {onAction} {preAction} />
|
||||
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
|
||||
{/if}
|
||||
<hr />
|
||||
<MenuOption
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
import { getAssetJobMessage, getSharedLink, handlePromiseError } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { SlideshowHistory } from '$lib/utils/slideshow-history';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import {
|
||||
AssetJobName,
|
||||
AssetTypeEnum,
|
||||
|
|
@ -47,7 +49,7 @@
|
|||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
preloadAssets?: AssetResponseDto[];
|
||||
preloadAssets?: TimelineAsset[];
|
||||
showNavigation?: boolean;
|
||||
withStacked?: boolean;
|
||||
isShared?: boolean;
|
||||
|
|
@ -56,10 +58,10 @@
|
|||
preAction?: PreAction | undefined;
|
||||
onAction?: OnAction | undefined;
|
||||
showCloseButton?: boolean;
|
||||
onClose: (dto: { asset: AssetResponseDto }) => void;
|
||||
onClose: (asset: AssetResponseDto) => void;
|
||||
onNext: () => Promise<HasAsset>;
|
||||
onPrevious: () => Promise<HasAsset>;
|
||||
onRandom: () => Promise<AssetResponseDto | undefined>;
|
||||
onRandom: () => Promise<{ id: string } | undefined>;
|
||||
copyImage?: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +83,7 @@
|
|||
copyImage = $bindable(),
|
||||
}: Props = $props();
|
||||
|
||||
const { setAsset } = assetViewingStore;
|
||||
const { setAssetId } = assetViewingStore;
|
||||
const {
|
||||
restartProgress: restartSlideshowProgress,
|
||||
stopProgress: stopSlideshowProgress,
|
||||
|
|
@ -121,7 +123,7 @@
|
|||
|
||||
untrack(() => {
|
||||
if (stack && stack?.assets.length > 1) {
|
||||
preloadAssets.push(stack.assets[1]);
|
||||
preloadAssets.push(toTimelineAsset(stack.assets[1]));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -161,7 +163,7 @@
|
|||
slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
|
||||
if (value === SlideshowState.PlaySlideshow) {
|
||||
slideshowHistory.reset();
|
||||
slideshowHistory.queue(asset);
|
||||
slideshowHistory.queue(toTimelineAsset(asset));
|
||||
handlePromiseError(handlePlaySlideshow());
|
||||
} else if (value === SlideshowState.StopSlideshow) {
|
||||
handlePromiseError(handleStopSlideshow());
|
||||
|
|
@ -171,7 +173,7 @@
|
|||
shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => {
|
||||
if (value === SlideshowNavigation.Shuffle) {
|
||||
slideshowHistory.reset();
|
||||
slideshowHistory.queue(asset);
|
||||
slideshowHistory.queue(toTimelineAsset(asset));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -225,7 +227,7 @@
|
|||
};
|
||||
|
||||
const closeViewer = () => {
|
||||
onClose({ asset });
|
||||
onClose(asset);
|
||||
};
|
||||
|
||||
const closeEditor = () => {
|
||||
|
|
@ -292,8 +294,7 @@
|
|||
let assetViewerHtmlElement = $state<HTMLElement>();
|
||||
|
||||
const slideshowHistory = new SlideshowHistory((asset) => {
|
||||
setAsset(asset);
|
||||
$restartSlideshowProgress = true;
|
||||
handlePromiseError(setAssetId(asset.id).then(() => ($restartSlideshowProgress = true)));
|
||||
});
|
||||
|
||||
const handleVideoStarted = () => {
|
||||
|
|
@ -563,8 +564,8 @@
|
|||
imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }}
|
||||
brokenAssetClass="text-xs"
|
||||
dimmed={stackedAsset.id !== asset.id}
|
||||
asset={stackedAsset}
|
||||
onClick={(stackedAsset) => {
|
||||
asset={toTimelineAsset(stackedAsset)}
|
||||
onClick={() => {
|
||||
asset = stackedAsset;
|
||||
}}
|
||||
onMouseEvent={({ isMouseOver }) => handleStackedAssetMouseEvent(isMouseOver, stackedAsset)}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
resetGlobalCropStore,
|
||||
rotateDegrees,
|
||||
} from '$lib/stores/asset-editor.store';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { animateCropChange, recalculateCrop } from './crop-settings';
|
||||
import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store';
|
||||
|
|
@ -81,7 +82,7 @@
|
|||
aria-label="Crop area"
|
||||
type="button"
|
||||
>
|
||||
<img draggable="false" src={img?.src} alt={$getAltText(asset)} />
|
||||
<img draggable="false" src={img?.src} alt={$getAltText(toTimelineAsset(asset))} />
|
||||
<div class={`${$isResizingOrDragging ? 'resizing' : ''} crop-frame`} bind:this={$cropFrame}>
|
||||
<div class="grid"></div>
|
||||
<div class="corner top-left"></div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { zoomImageAction, zoomed } from '$lib/actions/zoom-image';
|
||||
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
|
||||
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
||||
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
||||
import { photoViewerImgElement, type TimelineAsset } from '$lib/stores/assets-store.svelte';
|
||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||
|
|
@ -13,9 +13,10 @@
|
|||
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||
import { cancelImageUrl, preloadImageUrl } from '$lib/utils/sw-messaging';
|
||||
import { cancelImageUrl } from '$lib/utils/sw-messaging';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { swipe, type SwipeCustomEvent } from 'svelte-gestures';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
preloadAssets?: AssetResponseDto[] | undefined;
|
||||
preloadAssets?: TimelineAsset[] | undefined;
|
||||
element?: HTMLDivElement | undefined;
|
||||
haveFadeTransition?: boolean;
|
||||
sharedLink?: SharedLinkResponseDto | undefined;
|
||||
|
|
@ -69,10 +70,11 @@
|
|||
$boundingBoxesArray = [];
|
||||
});
|
||||
|
||||
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: AssetResponseDto[]) => {
|
||||
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: TimelineAsset[]) => {
|
||||
for (const preloadAsset of preloadAssets || []) {
|
||||
if (preloadAsset.type === AssetTypeEnum.Image) {
|
||||
preloadImageUrl(getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash));
|
||||
if (preloadAsset.isImage) {
|
||||
let img = new Image();
|
||||
img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -197,7 +199,7 @@
|
|||
bind:clientWidth={containerWidth}
|
||||
bind:clientHeight={containerHeight}
|
||||
>
|
||||
<img style="display:none" src={imageLoaderUrl} alt={$getAltText(asset)} {onload} {onerror} />
|
||||
<img style="display:none" src={imageLoaderUrl} alt="" {onload} {onerror} />
|
||||
{#if !imageLoaded}
|
||||
<div id="spinner" class="flex h-full items-center justify-center">
|
||||
<LoadingSpinner />
|
||||
|
|
@ -213,7 +215,7 @@
|
|||
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
|
||||
<img
|
||||
src={assetFileUrl}
|
||||
alt={$getAltText(asset)}
|
||||
alt=""
|
||||
class="absolute top-0 start-0 object-cover h-full w-full blur-lg"
|
||||
draggable="false"
|
||||
/>
|
||||
|
|
@ -221,7 +223,7 @@
|
|||
<img
|
||||
bind:this={$photoViewerImgElement}
|
||||
src={assetFileUrl}
|
||||
alt={$getAltText(asset)}
|
||||
alt={$getAltText(toTimelineAsset(asset))}
|
||||
class="h-full w-full {$slideshowState === SlideshowState.None
|
||||
? 'object-contain'
|
||||
: slideshowLookCssMapping[$slideshowLook]}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue