refactor: rename TimelineAsset -> Asset

This commit is contained in:
midzelis 2025-10-08 23:54:07 +00:00
parent 53680d9643
commit c6e1170e6d
57 changed files with 260 additions and 281 deletions

View file

@ -1,24 +1,24 @@
import type { AssetAction } from '$lib/constants'; import type { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import type { AlbumResponseDto, AssetResponseDto, PersonResponseDto, StackResponseDto } from '@immich/sdk'; import type { AlbumResponseDto, AssetResponseDto, PersonResponseDto, StackResponseDto } from '@immich/sdk';
type ActionMap = { type ActionMap = {
[AssetAction.ARCHIVE]: { asset: TimelineAsset }; [AssetAction.ARCHIVE]: { asset: Asset };
[AssetAction.UNARCHIVE]: { asset: TimelineAsset }; [AssetAction.UNARCHIVE]: { asset: Asset };
[AssetAction.FAVORITE]: { asset: TimelineAsset }; [AssetAction.FAVORITE]: { asset: Asset };
[AssetAction.UNFAVORITE]: { asset: TimelineAsset }; [AssetAction.UNFAVORITE]: { asset: Asset };
[AssetAction.TRASH]: { asset: TimelineAsset }; [AssetAction.TRASH]: { asset: Asset };
[AssetAction.DELETE]: { asset: TimelineAsset }; [AssetAction.DELETE]: { asset: Asset };
[AssetAction.RESTORE]: { asset: TimelineAsset }; [AssetAction.RESTORE]: { asset: Asset };
[AssetAction.ADD]: { asset: TimelineAsset }; [AssetAction.ADD]: { asset: Asset };
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto }; [AssetAction.ADD_TO_ALBUM]: { asset: Asset; album: AlbumResponseDto };
[AssetAction.STACK]: { stack: StackResponseDto }; [AssetAction.STACK]: { stack: StackResponseDto };
[AssetAction.UNSTACK]: { assets: TimelineAsset[] }; [AssetAction.UNSTACK]: { assets: Asset[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset }; [AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: Asset };
[AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto }; [AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto };
[AssetAction.REMOVE_ASSET_FROM_STACK]: { stack: StackResponseDto | null; asset: AssetResponseDto }; [AssetAction.REMOVE_ASSET_FROM_STACK]: { stack: StackResponseDto | null; asset: AssetResponseDto };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset }; [AssetAction.SET_VISIBILITY_LOCKED]: { asset: Asset };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset }; [AssetAction.SET_VISIBILITY_TIMELINE]: { asset: Asset };
[AssetAction.SET_PERSON_FEATURED_PHOTO]: { asset: AssetResponseDto; person: PersonResponseDto }; [AssetAction.SET_PERSON_FEATURED_PHOTO]: { asset: AssetResponseDto; person: PersonResponseDto };
}; };

View file

@ -5,7 +5,7 @@
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte'; import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
import { addAssetsToAlbum, addAssetsToAlbums } from '$lib/utils/asset-utils'; import { addAssetsToAlbum, addAssetsToAlbums } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk'; import type { AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js'; import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
@ -29,13 +29,13 @@
if (albums.length === 1) { if (albums.length === 1) {
const album = albums[0]; const album = albums[0];
await addAssetsToAlbum(album.id, [asset.id]); await addAssetsToAlbum(album.id, [asset.id]);
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album }); onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toAsset(asset), album });
} else { } else {
await addAssetsToAlbums( await addAssetsToAlbums(
albums.map((a) => a.id), albums.map((a) => a.id),
[asset.id], [asset.id],
); );
onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toTimelineAsset(asset), album: albums[0] }); onAction({ type: AssetAction.ADD_TO_ALBUM, asset: toAsset(asset), album: albums[0] });
} }
}; };
</script> </script>

View file

@ -4,7 +4,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { toggleArchive } from '$lib/utils/asset-utils'; import { toggleArchive } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk'; import type { AssetResponseDto } from '@immich/sdk';
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline } from '@mdi/js'; import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -19,11 +19,11 @@
const onArchive = async () => { const onArchive = async () => {
if (!asset.isArchived) { if (!asset.isArchived) {
preAction({ type: AssetAction.ARCHIVE, asset: toTimelineAsset(asset) }); preAction({ type: AssetAction.ARCHIVE, asset: toAsset(asset) });
} }
const updatedAsset = await toggleArchive(asset); const updatedAsset = await toggleArchive(asset);
if (updatedAsset) { if (updatedAsset) {
onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset: toTimelineAsset(asset) }); onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset: toAsset(asset) });
} }
}; };
</script> </script>

View file

@ -10,7 +10,7 @@
import { showDeleteModal } from '$lib/stores/preferences.store'; import { showDeleteModal } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { deleteAssets, type AssetResponseDto } from '@immich/sdk'; import { deleteAssets, type AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js'; import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js';
@ -43,9 +43,9 @@
const trashAsset = async () => { const trashAsset = async () => {
try { try {
preAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) }); preAction({ type: AssetAction.TRASH, asset: toAsset(asset) });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } }); await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
onAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) }); onAction({ type: AssetAction.TRASH, asset: toAsset(asset) });
notificationController.show({ notificationController.show({
message: $t('moved_to_trash'), message: $t('moved_to_trash'),
@ -58,9 +58,9 @@
const deleteAsset = async () => { const deleteAsset = async () => {
try { try {
preAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) }); preAction({ type: AssetAction.DELETE, asset: toAsset(asset) });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } }); await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
onAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) }); onAction({ type: AssetAction.DELETE, asset: toAsset(asset) });
notificationController.show({ notificationController.show({
message: $t('permanently_deleted_asset'), message: $t('permanently_deleted_asset'),

View file

@ -2,7 +2,7 @@
import { shortcut } from '$lib/actions/shortcut'; import { shortcut } from '$lib/actions/shortcut';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { downloadFile } from '$lib/utils/asset-utils'; import { downloadFile } from '$lib/utils/asset-utils';
import { getAssetInfo } from '@immich/sdk'; import { getAssetInfo } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
@ -10,7 +10,7 @@
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
interface Props { interface Props {
asset: TimelineAsset; asset: Asset;
menuItem?: boolean; menuItem?: boolean;
} }

View file

@ -6,12 +6,12 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { updateAsset, type AssetResponseDto } from '@immich/sdk'; import { updateAsset, type AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { mdiHeart, mdiHeartOutline } from '@mdi/js'; import { mdiHeart, mdiHeartOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { OnAction } from './action'; import type { OnAction } from './action';
import { IconButton } from '@immich/ui';
interface Props { interface Props {
asset: AssetResponseDto; asset: AssetResponseDto;
@ -33,7 +33,7 @@
onAction({ onAction({
type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE, type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE,
asset: toTimelineAsset(asset), asset: toAsset(asset),
}); });
notificationController.show({ notificationController.show({

View file

@ -2,7 +2,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { keepThisDeleteOthers } from '$lib/utils/asset-utils'; import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto, StackResponseDto } from '@immich/sdk'; import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
import { mdiPinOutline } from '@mdi/js'; import { mdiPinOutline } from '@mdi/js';
@ -30,7 +30,7 @@
const keptAsset = await keepThisDeleteOthers(asset, stack); const keptAsset = await keepThisDeleteOthers(asset, stack);
if (keptAsset) { if (keptAsset) {
onAction({ type: AssetAction.UNSTACK, assets: [toTimelineAsset(keptAsset)] }); onAction({ type: AssetAction.UNSTACK, assets: [toAsset(keptAsset)] });
} }
}; };
</script> </script>

View file

@ -6,7 +6,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { restoreAssets, type AssetResponseDto } from '@immich/sdk'; import { restoreAssets, type AssetResponseDto } from '@immich/sdk';
import { mdiHistory } from '@mdi/js'; import { mdiHistory } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -24,7 +24,7 @@
await restoreAssets({ bulkIdsDto: { ids: [asset.id] } }); await restoreAssets({ bulkIdsDto: { ids: [asset.id] } });
asset.isTrashed = false; asset.isTrashed = false;
onAction({ type: AssetAction.RESTORE, asset: toTimelineAsset(asset) }); onAction({ type: AssetAction.RESTORE, asset: toAsset(asset) });
notificationController.show({ notificationController.show({
type: NotificationType.Info, type: NotificationType.Info,

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { AssetVisibility, updateAssets } from '@immich/sdk'; import { AssetVisibility, updateAssets } from '@immich/sdk';
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
@ -10,7 +10,7 @@
import type { OnAction, PreAction } from './action'; import type { OnAction, PreAction } from './action';
interface Props { interface Props {
asset: TimelineAsset; asset: Asset;
onAction: OnAction; onAction: OnAction;
preAction: PreAction; preAction: PreAction;
} }

View file

@ -2,7 +2,7 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { deleteStack } from '$lib/utils/asset-utils'; import { deleteStack } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import type { StackResponseDto } from '@immich/sdk'; import type { StackResponseDto } from '@immich/sdk';
import { mdiImageOffOutline } from '@mdi/js'; import { mdiImageOffOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -18,7 +18,7 @@
const handleUnstack = async () => { const handleUnstack = async () => {
const unstackedAssets = await deleteStack([stack.id]); const unstackedAssets = await deleteStack([stack.id]);
if (unstackedAssets) { if (unstackedAssets) {
onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets.map((asset) => toTimelineAsset(asset)) }); onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets.map((asset) => toAsset(asset)) });
} }
}; };
</script> </script>

View file

@ -31,7 +31,7 @@
import { getAssetJobName, getSharedLink } from '$lib/utils'; import { getAssetJobName, getSharedLink } from '$lib/utils';
import { canCopyImageToClipboard } from '$lib/utils/asset-utils'; import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { import {
AssetJobName, AssetJobName,
AssetTypeEnum, AssetTypeEnum,
@ -165,7 +165,7 @@
{/if} {/if}
{#if !isOwner && showDownloadButton} {#if !isOwner && showDownloadButton}
<DownloadAction asset={toTimelineAsset(asset)} /> <DownloadAction asset={toAsset(asset)} />
{/if} {/if}
{#if showDetailButton} {#if showDetailButton}
@ -184,7 +184,7 @@
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} /> <MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if} {/if}
{#if showDownloadButton} {#if showDownloadButton}
<DownloadAction asset={toTimelineAsset(asset)} menuItem /> <DownloadAction asset={toAsset(asset)} menuItem />
{/if} {/if}
{#if !isLocked} {#if !isLocked}
@ -243,7 +243,7 @@
{/if} {/if}
{#if !asset.isTrashed} {#if !asset.isTrashed}
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} /> <SetVisibilityAction asset={toAsset(asset)} {onAction} {preAction} />
{/if} {/if}
<hr /> <hr />
<MenuOption <MenuOption

View file

@ -8,7 +8,7 @@
import { AssetAction, ProjectionType } from '$lib/constants'; import { AssetAction, ProjectionType } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte'; import { activityManager } from '$lib/managers/activity-manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isShowDetail } from '$lib/stores/preferences.store'; import { isShowDetail } from '$lib/stores/preferences.store';
@ -18,12 +18,12 @@
import { getAssetJobMessage, getSharedLink, handlePromiseError } from '$lib/utils'; import { getAssetJobMessage, getSharedLink, handlePromiseError } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { SlideshowHistory } from '$lib/utils/slideshow-history'; import { SlideshowHistory } from '$lib/utils/slideshow-history';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { import {
AssetJobName, AssetJobName,
AssetTypeEnum, AssetTypeEnum,
getAssetInfo,
getAllAlbums, getAllAlbums,
getAssetInfo,
getStack, getStack,
runAssetJobs, runAssetJobs,
type AlbumResponseDto, type AlbumResponseDto,
@ -50,7 +50,7 @@
interface Props { interface Props {
asset: AssetResponseDto; asset: AssetResponseDto;
preloadAssets?: TimelineAsset[]; preloadAssets?: Asset[];
showNavigation?: boolean; showNavigation?: boolean;
withStacked?: boolean; withStacked?: boolean;
isShared?: boolean; isShared?: boolean;
@ -126,7 +126,7 @@
untrack(() => { untrack(() => {
if (stack && stack?.assets.length > 1) { if (stack && stack?.assets.length > 1) {
preloadAssets.push(toTimelineAsset(stack.assets[1])); preloadAssets.push(toAsset(stack.assets[1]));
} }
}); });
}; };
@ -156,7 +156,7 @@
slideshowStateUnsubscribe = slideshowState.subscribe((value) => { slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
if (value === SlideshowState.PlaySlideshow) { if (value === SlideshowState.PlaySlideshow) {
slideshowHistory.reset(); slideshowHistory.reset();
slideshowHistory.queue(toTimelineAsset(asset)); slideshowHistory.queue(toAsset(asset));
handlePromiseError(handlePlaySlideshow()); handlePromiseError(handlePlaySlideshow());
} else if (value === SlideshowState.StopSlideshow) { } else if (value === SlideshowState.StopSlideshow) {
handlePromiseError(handleStopSlideshow()); handlePromiseError(handleStopSlideshow());
@ -166,7 +166,7 @@
shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => { shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => {
if (value === SlideshowNavigation.Shuffle) { if (value === SlideshowNavigation.Shuffle) {
slideshowHistory.reset(); slideshowHistory.reset();
slideshowHistory.queue(toTimelineAsset(asset)); slideshowHistory.queue(toAsset(asset));
} }
}); });
@ -571,7 +571,7 @@
imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }} imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }}
brokenAssetClass="text-xs" brokenAssetClass="text-xs"
dimmed={stackedAsset.id !== asset.id} dimmed={stackedAsset.id !== asset.id}
asset={toTimelineAsset(stackedAsset)} asset={toAsset(stackedAsset)}
onClick={() => { onClick={() => {
asset = stackedAsset; asset = stackedAsset;
previewStackedAsset = undefined; previewStackedAsset = undefined;

View file

@ -12,7 +12,7 @@
resetGlobalCropStore, resetGlobalCropStore,
rotateDegrees, rotateDegrees,
} from '$lib/stores/asset-editor.store'; } from '$lib/stores/asset-editor.store';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk'; import type { AssetResponseDto } from '@immich/sdk';
import { animateCropChange, recalculateCrop } from './crop-settings'; import { animateCropChange, recalculateCrop } from './crop-settings';
import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store'; import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store';
@ -80,7 +80,7 @@
aria-label="Crop area" aria-label="Crop area"
type="button" type="button"
> >
<img draggable="false" src={img?.src} alt={$getAltText(toTimelineAsset(asset))} /> <img draggable="false" src={img?.src} alt={$getAltText(toAsset(asset))} />
<div class={`${$isResizingOrDragging ? 'resizing' : ''} crop-frame`} bind:this={$cropFrame}> <div class={`${$isResizingOrDragging ? 'resizing' : ''} crop-frame`} bind:this={$cropFrame}>
<div class="grid"></div> <div class="grid"></div>
<div class="corner top-left"></div> <div class="corner top-left"></div>

View file

@ -5,7 +5,7 @@
import BrokenAsset from '$lib/components/assets/broken-asset.svelte'; import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import { castManager } from '$lib/managers/cast-manager.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store'; import { boundingBoxesArray } from '$lib/stores/people.store';
@ -18,7 +18,7 @@
import { getBoundingBox } from '$lib/utils/people-utils'; import { getBoundingBox } from '$lib/utils/people-utils';
import { cancelImageUrl } from '$lib/utils/sw-messaging'; import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui'; import { LoadingSpinner } from '@immich/ui';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
@ -29,7 +29,7 @@
interface Props { interface Props {
asset: AssetResponseDto; asset: AssetResponseDto;
preloadAssets?: TimelineAsset[] | undefined; preloadAssets?: Asset[] | undefined;
element?: HTMLDivElement | undefined; element?: HTMLDivElement | undefined;
haveFadeTransition?: boolean; haveFadeTransition?: boolean;
sharedLink?: SharedLinkResponseDto | undefined; sharedLink?: SharedLinkResponseDto | undefined;
@ -72,7 +72,7 @@
$boundingBoxesArray = []; $boundingBoxesArray = [];
}); });
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: TimelineAsset[]) => { const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: Asset[]) => {
for (const preloadAsset of preloadAssets || []) { for (const preloadAsset of preloadAssets || []) {
if (preloadAsset.isImage) { if (preloadAsset.isImage) {
let img = new Image(); let img = new Image();
@ -249,7 +249,7 @@
<img <img
bind:this={$photoViewerImgElement} bind:this={$photoViewerImgElement}
src={assetFileUrl} src={assetFileUrl}
alt={$getAltText(toTimelineAsset(asset))} alt={$getAltText(toAsset(asset))}
class="h-full w-full {$slideshowState === SlideshowState.None class="h-full w-full {$slideshowState === SlideshowState.None
? 'object-contain' ? 'object-contain'
: slideshowLookCssMapping[$slideshowLook]}" : slideshowLookCssMapping[$slideshowLook]}"

View file

@ -17,7 +17,7 @@
import { thumbhash } from '$lib/actions/thumbhash'; import { thumbhash } from '$lib/actions/thumbhash';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { moveFocus } from '$lib/utils/focus-util'; import { moveFocus } from '$lib/utils/focus-util';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
@ -30,7 +30,7 @@
import VideoThumbnail from './video-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte';
interface Props { interface Props {
asset: TimelineAsset; asset: Asset;
groupIndex?: number; groupIndex?: number;
thumbnailSize?: number; thumbnailSize?: number;
thumbnailWidth?: number; thumbnailWidth?: number;
@ -45,8 +45,8 @@
imageClass?: ClassValue; imageClass?: ClassValue;
brokenAssetClass?: ClassValue; brokenAssetClass?: ClassValue;
dimmed?: boolean; dimmed?: boolean;
onClick?: (asset: TimelineAsset) => void; onClick?: (asset: Asset) => void;
onSelect?: (asset: TimelineAsset) => void; onSelect?: (asset: Asset) => void;
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void; onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
} }

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetThumbnailUrl } from '$lib/utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize } from '@immich/sdk'; import { AssetMediaSize } from '@immich/sdk';
@ -9,7 +9,7 @@
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
interface Props { interface Props {
asset: TimelineAsset; asset: Asset;
onImageLoad: () => void; onImageLoad: () => void;
} }

View file

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils';
import { AssetMediaSize } from '@immich/sdk'; import { AssetMediaSize } from '@immich/sdk';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
interface Props { interface Props {
asset: TimelineAsset; asset: Asset;
videoPlayer: HTMLVideoElement | undefined; videoPlayer: HTMLVideoElement | undefined;
videoViewerMuted?: boolean; videoViewerMuted?: boolean;
videoViewerVolume?: number; videoViewerVolume?: number;

View file

@ -27,7 +27,7 @@
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants'; import { AppRoute, QueryParameter } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import type { Asset, Viewport } from '$lib/managers/timeline-manager/types';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { type MemoryAsset, memoryStore } from '$lib/stores/memory.store.svelte'; import { type MemoryAsset, memoryStore } from '$lib/stores/memory.store.svelte';
@ -35,7 +35,7 @@
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
import { cancelMultiselect } from '$lib/utils/asset-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils';
import { fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util'; import { fromISODateTimeUTC, toAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, getAssetInfo } from '@immich/sdk'; import { AssetMediaSize, getAssetInfo } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { import {
@ -71,7 +71,7 @@
let currentMemoryAssetFull = $derived.by(async () => let currentMemoryAssetFull = $derived.by(async () =>
current?.asset ? await getAssetInfo({ ...authManager.params, id: current.asset.id }) : undefined, current?.asset ? await getAssetInfo({ ...authManager.params, id: current.asset.id }) : undefined,
); );
let currentTimelineAssets = $derived(current?.memory.assets.map((asset) => toTimelineAsset(asset)) || []); let currentAssets = $derived(current?.memory.assets.map((asset) => toAsset(asset)) || []);
let isSaved = $derived(current?.memory.isSaved); let isSaved = $derived(current?.memory.isSaved);
let viewerHeight = $state(0); let viewerHeight = $state(0);
@ -97,7 +97,7 @@
await goto(asHref(asset)); await goto(asHref(asset));
}; };
const setProgressDuration = (asset: TimelineAsset) => { const setProgressDuration = (asset: Asset) => {
if (asset.isVideo) { if (asset.isVideo) {
const timeParts = asset.duration!.split(':').map(Number); const timeParts = asset.duration!.split(':').map(Number);
const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000; const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
@ -116,8 +116,7 @@
const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]); const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]);
const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]); const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]);
const handleEscape = async () => goto(AppRoute.PHOTOS); const handleEscape = async () => goto(AppRoute.PHOTOS);
const handleSelectAll = () => const handleSelectAll = () => assetInteraction.selectAssets(current?.memory.assets.map((a) => toAsset(a)) || []);
assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []);
const handleAction = async (callingContext: string, action: 'reset' | 'pause' | 'play') => { const handleAction = async (callingContext: string, action: 'reset' | 'pause' | 'play') => {
// leaving these log statements here as comments. Very useful to figure out what's going on during dev! // leaving these log statements here as comments. Very useful to figure out what's going on during dev!
@ -658,7 +657,7 @@
<GalleryViewer <GalleryViewer
onNext={handleNextAsset} onNext={handleNextAsset}
onPrevious={handlePreviousAsset} onPrevious={handlePreviousAsset}
assets={currentTimelineAssets} assets={currentAssets}
viewport={galleryViewport} viewport={galleryViewport}
{assetInteraction} {assetInteraction}
slidingWindowOffset={viewerHeight} slidingWindowOffset={viewerHeight}

View file

@ -4,7 +4,7 @@
import { memoryStore } from '$lib/stores/memory.store.svelte'; import { memoryStore } from '$lib/stores/memory.store.svelte';
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils'; import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -89,7 +89,7 @@
<img <img
class="h-full w-full rounded-xl object-cover" class="h-full w-full rounded-xl object-cover"
src={getAssetThumbnailUrl(memory.assets[0].id)} src={getAssetThumbnailUrl(memory.assets[0].id)}
alt={$t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } })} alt={$t('memory_lane_title', { values: { title: $getAltText(toAsset(memory.assets[0])) } })}
draggable="false" draggable="false"
/> />
<div <div

View file

@ -14,7 +14,7 @@
import { cancelMultiselect, downloadArchive } from '$lib/utils/asset-utils'; import { cancelMultiselect, downloadArchive } from '$lib/utils/asset-utils';
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader'; import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { addSharedLinkAssets, getAssetInfo, type SharedLinkResponseDto } from '@immich/sdk'; import { addSharedLinkAssets, getAssetInfo, type SharedLinkResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiArrowLeft, mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js'; import { mdiArrowLeft, mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js';
@ -34,7 +34,7 @@
const viewport: Viewport = $state({ width: 0, height: 0 }); const viewport: Viewport = $state({ width: 0, height: 0 });
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
let assets = $derived(sharedLink.assets.map((a) => toTimelineAsset(a))); let assets = $derived(sharedLink.assets.map((a) => toAsset(a)));
dragAndDropFilesStore.subscribe((value) => { dragAndDropFilesStore.subscribe((value) => {
if (value.isDragging && value.files.length > 0) { if (value.isDragging && value.files.length > 0) {

View file

@ -5,7 +5,7 @@
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import { AppRoute, AssetAction } from '$lib/constants'; import { AppRoute, AssetAction } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte'; import Portal from '$lib/elements/Portal.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import type { Asset, Viewport } from '$lib/managers/timeline-manager/types';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -18,7 +18,7 @@
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { getJustifiedLayoutFromAssets, type CommonJustifiedLayout } from '$lib/utils/layout-utils'; import { getJustifiedLayoutFromAssets, type CommonJustifiedLayout } from '$lib/utils/layout-utils';
import { navigate } from '$lib/utils/navigation'; import { navigate } from '$lib/utils/navigation';
import { isTimelineAsset, toTimelineAsset } from '$lib/utils/timeline-util'; import { isAsset, toAsset } from '$lib/utils/timeline-util';
import { AssetVisibility, type AssetResponseDto } from '@immich/sdk'; import { AssetVisibility, type AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui'; import { modalManager } from '@immich/ui';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
@ -28,7 +28,7 @@
interface Props { interface Props {
initialAssetId?: string; initialAssetId?: string;
assets: (TimelineAsset | AssetResponseDto)[]; assets: (Asset | AssetResponseDto)[];
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
disableAssetSelect?: boolean; disableAssetSelect?: boolean;
showArchiveIcon?: boolean; showArchiveIcon?: boolean;
@ -130,7 +130,7 @@
} }
let shiftKeyIsDown = $state(false); let shiftKeyIsDown = $state(false);
let lastAssetMouseEvent: TimelineAsset | null = $state(null); let lastAssetMouseEvent: Asset | null = $state(null);
let slidingWindow = $state({ top: 0, bottom: 0 }); let slidingWindow = $state({ top: 0, bottom: 0 });
const updateSlidingWindow = () => { const updateSlidingWindow = () => {
@ -157,14 +157,14 @@
} }
} }
}); });
const viewAssetHandler = async (asset: TimelineAsset) => { const viewAssetHandler = async (asset: Asset) => {
currentIndex = assets.findIndex((a) => a.id == asset.id); currentIndex = assets.findIndex((a) => a.id == asset.id);
await setAssetId(assets[currentIndex].id); await setAssetId(assets[currentIndex].id);
await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id });
}; };
const selectAllAssets = () => { const selectAllAssets = () => {
assetInteraction.selectAssets(assets.map((a) => toTimelineAsset(a))); assetInteraction.selectAssets(assets.map((a) => toAsset(a)));
}; };
const deselectAllAssets = () => { const deselectAllAssets = () => {
@ -186,7 +186,7 @@
} }
}; };
const handleSelectAssets = (asset: TimelineAsset) => { const handleSelectAssets = (asset: Asset) => {
if (!asset) { if (!asset) {
return; return;
} }
@ -209,14 +209,14 @@
assetInteraction.setAssetSelectionStart(deselect ? null : asset); assetInteraction.setAssetSelectionStart(deselect ? null : asset);
}; };
const handleSelectAssetCandidates = (asset: TimelineAsset | null) => { const handleSelectAssetCandidates = (asset: Asset | null) => {
if (asset) { if (asset) {
selectAssetCandidates(asset); selectAssetCandidates(asset);
} }
lastAssetMouseEvent = asset; lastAssetMouseEvent = asset;
}; };
const selectAssetCandidates = (endAsset: TimelineAsset) => { const selectAssetCandidates = (endAsset: Asset) => {
if (!shiftKeyIsDown) { if (!shiftKeyIsDown) {
return; return;
} }
@ -233,7 +233,7 @@
[start, end] = [end, start]; [start, end] = [end, start];
} }
assetInteraction.setAssetSelectionCandidates(assets.slice(start, end + 1).map((a) => toTimelineAsset(a))); assetInteraction.setAssetSelectionCandidates(assets.slice(start, end + 1).map((a) => toAsset(a)));
}; };
const onSelectStart = (event: Event) => { const onSelectStart = (event: Event) => {
@ -434,7 +434,7 @@
} }
}; };
const assetMouseEventHandler = (asset: TimelineAsset | null) => { const assetMouseEventHandler = (asset: Asset | null) => {
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {
handleSelectAssetCandidates(asset); handleSelectAssetCandidates(asset);
} }
@ -496,21 +496,21 @@
readonly={disableAssetSelect} readonly={disableAssetSelect}
onClick={() => { onClick={() => {
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {
handleSelectAssets(toTimelineAsset(currentAsset)); handleSelectAssets(toAsset(currentAsset));
return; return;
} }
void viewAssetHandler(toTimelineAsset(currentAsset)); void viewAssetHandler(toAsset(currentAsset));
}} }}
onSelect={() => handleSelectAssets(toTimelineAsset(currentAsset))} onSelect={() => handleSelectAssets(toAsset(currentAsset))}
onMouseEvent={() => assetMouseEventHandler(toTimelineAsset(currentAsset))} onMouseEvent={() => assetMouseEventHandler(toAsset(currentAsset))}
{showArchiveIcon} {showArchiveIcon}
asset={toTimelineAsset(currentAsset)} asset={toAsset(currentAsset)}
selected={assetInteraction.hasSelectedAsset(currentAsset.id)} selected={assetInteraction.hasSelectedAsset(currentAsset.id)}
selectionCandidate={assetInteraction.hasSelectionCandidate(currentAsset.id)} selectionCandidate={assetInteraction.hasSelectionCandidate(currentAsset.id)}
thumbnailWidth={layout.width} thumbnailWidth={layout.width}
thumbnailHeight={layout.height} thumbnailHeight={layout.height}
/> />
{#if showAssetName && !isTimelineAsset(currentAsset)} {#if showAssetName && !isAsset(currentAsset)}
<div <div
class="absolute text-center p-1 text-xs font-mono font-semibold w-full bottom-0 bg-linear-to-t bg-slate-50/75 dark:bg-slate-800/75 overflow-clip text-ellipsis whitespace-pre-wrap" class="absolute text-center p-1 text-xs font-mono font-semibold w-full bottom-0 bg-linear-to-t bg-slate-50/75 dark:bg-slate-800/75 overflow-clip text-ellipsis whitespace-pre-wrap"
> >

View file

@ -4,8 +4,8 @@
export interface AssetControlContext { export interface AssetControlContext {
// Wrap assets in a function, because context isn't reactive. // Wrap assets in a function, because context isn't reactive.
getAssets: () => TimelineAsset[]; // All assets includes partners' assets getAssets: () => Asset[]; // All assets includes partners' assets
getOwnedAssets: () => TimelineAsset[]; // Only assets owned by the user getOwnedAssets: () => Asset[]; // Only assets owned by the user
clearSelect: () => void; clearSelect: () => void;
} }
@ -14,13 +14,13 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { mdiClose } from '@mdi/js'; import { mdiClose } from '@mdi/js';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ControlAppBar from '../shared-components/control-app-bar.svelte';
interface Props { interface Props {
assets: TimelineAsset[]; assets: Asset[];
clearSelect: () => void; clearSelect: () => void;
ownerId?: string | undefined; ownerId?: string | undefined;
children?: Snippet; children?: Snippet;

View file

@ -3,7 +3,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer'; import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer';
import Scrubber from '$lib/components/timeline/Scrubber.svelte'; import Scrubber from '$lib/components/timeline/Scrubber.svelte';
import TimelineAssetViewer from '$lib/components/timeline/TimelineAssetViewer.svelte'; import AssetViewer from '$lib/components/timeline/TimelineAssetViewer.svelte';
import TimelineKeyboardActions from '$lib/components/timeline/actions/TimelineKeyboardActions.svelte'; import TimelineKeyboardActions from '$lib/components/timeline/actions/TimelineKeyboardActions.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import HotModuleReload from '$lib/elements/HotModuleReload.svelte'; import HotModuleReload from '$lib/elements/HotModuleReload.svelte';
@ -12,7 +12,7 @@
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte'; import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -48,21 +48,16 @@
album?: AlbumResponseDto | null; album?: AlbumResponseDto | null;
person?: PersonResponseDto | null; person?: PersonResponseDto | null;
isShowDeleteConfirmation?: boolean; isShowDeleteConfirmation?: boolean;
onSelect?: (asset: TimelineAsset) => void; onSelect?: (asset: Asset) => void;
onEscape?: () => void; onEscape?: () => void;
children?: Snippet; children?: Snippet;
empty?: Snippet; empty?: Snippet;
customLayout?: Snippet<[TimelineAsset]>; customLayout?: Snippet<[Asset]>;
onThumbnailClick?: ( onThumbnailClick?: (
asset: TimelineAsset, asset: Asset,
timelineManager: TimelineManager, timelineManager: TimelineManager,
dayGroup: DayGroup, dayGroup: DayGroup,
onClick: ( onClick: (timelineManager: TimelineManager, assets: Asset[], groupTitle: string, asset: Asset) => void,
timelineManager: TimelineManager,
assets: TimelineAsset[],
groupTitle: string,
asset: TimelineAsset,
) => void,
) => void; ) => void;
} }
@ -182,7 +177,7 @@
return true; return true;
}; };
const scrollToAsset = (asset: TimelineAsset) => { const scrollToAsset = (asset: Asset) => {
const monthGroup = timelineManager.getMonthGroupByAssetId(asset.id); const monthGroup = timelineManager.getMonthGroupByAssetId(asset.id);
if (!monthGroup) { if (!monthGroup) {
return false; return false;
@ -384,13 +379,13 @@
} }
}; };
const handleSelectAsset = (asset: TimelineAsset) => { const handleSelectAsset = (asset: Asset) => {
if (!timelineManager.albumAssets.has(asset.id)) { if (!timelineManager.albumAssets.has(asset.id)) {
assetInteraction.selectAsset(asset); assetInteraction.selectAsset(asset);
} }
}; };
let lastAssetMouseEvent: TimelineAsset | null = $state(null); let lastAssetMouseEvent: Asset | null = $state(null);
let shiftKeyIsDown = $state(false); let shiftKeyIsDown = $state(false);
@ -407,14 +402,14 @@
shiftKeyIsDown = false; shiftKeyIsDown = false;
} }
}; };
const handleSelectAssetCandidates = (asset: TimelineAsset | null) => { const handleSelectAssetCandidates = (asset: Asset | null) => {
if (asset) { if (asset) {
void selectAssetCandidates(asset); void selectAssetCandidates(asset);
} }
lastAssetMouseEvent = asset; lastAssetMouseEvent = asset;
}; };
const handleGroupSelect = (timelineManager: TimelineManager, group: string, assets: TimelineAsset[]) => { const handleGroupSelect = (timelineManager: TimelineManager, group: string, assets: Asset[]) => {
if (assetInteraction.selectedGroup.has(group)) { if (assetInteraction.selectedGroup.has(group)) {
assetInteraction.removeGroupFromMultiselectGroup(group); assetInteraction.removeGroupFromMultiselectGroup(group);
for (const asset of assets) { for (const asset of assets) {
@ -434,7 +429,7 @@
} }
}; };
const handleSelectAssets = async (asset: TimelineAsset) => { const handleSelectAssets = async (asset: Asset) => {
if (!asset) { if (!asset) {
return; return;
} }
@ -518,7 +513,7 @@
assetInteraction.setAssetSelectionStart(deselect ? null : asset); assetInteraction.setAssetSelectionStart(deselect ? null : asset);
}; };
const selectAssetCandidates = async (endAsset: TimelineAsset) => { const selectAssetCandidates = async (endAsset: Asset) => {
if (!shiftKeyIsDown) { if (!shiftKeyIsDown) {
return; return;
} }
@ -686,7 +681,7 @@
<Portal target="body"> <Portal target="body">
{#if $showAssetViewer} {#if $showAssetViewer}
<TimelineAssetViewer bind:showSkeleton {timelineManager} {removeAction} {withStacked} {isShared} {album} {person} /> <AssetViewer bind:showSkeleton {timelineManager} {removeAction} {withStacked} {isShared} {album} {person} />
{/if} {/if}
</Portal> </Portal>

View file

@ -6,7 +6,7 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions'; import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
import { navigate } from '$lib/utils/navigation'; import { navigate } from '$lib/utils/navigation';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; import { getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
let { asset: viewingAsset, gridScrollTarget, mutex, preloadAssets } = assetViewingStore; let { asset: viewingAsset, gridScrollTarget, mutex, preloadAssets } = assetViewingStore;
@ -135,12 +135,12 @@
break; break;
} }
case AssetAction.REMOVE_ASSET_FROM_STACK: { case AssetAction.REMOVE_ASSET_FROM_STACK: {
timelineManager.addAssets([toTimelineAsset(action.asset)]); timelineManager.addAssets([toAsset(action.asset)]);
if (action.stack) { if (action.stack) {
//Have to unstack then restack assets in timeline in order to update the stack count in the timeline. //Have to unstack then restack assets in timeline in order to update the stack count in the timeline.
updateUnstackedAssetInTimeline( updateUnstackedAssetInTimeline(
timelineManager, timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)), action.stack.assets.map((asset) => toAsset(asset)),
); );
updateStackedAssetInTimeline(timelineManager, { updateStackedAssetInTimeline(timelineManager, {
stack: action.stack, stack: action.stack,
@ -155,7 +155,7 @@
//Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible. //Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible.
updateUnstackedAssetInTimeline( updateUnstackedAssetInTimeline(
timelineManager, timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)), action.stack.assets.map((asset) => toAsset(asset)),
); );
updateStackedAssetInTimeline(timelineManager, { updateStackedAssetInTimeline(timelineManager, {
stack: action.stack, stack: action.stack,

View file

@ -3,7 +3,7 @@
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte'; import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { assetSnapshot, assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; import { assetSnapshot, assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte'; import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
@ -28,22 +28,17 @@
monthGroup: MonthGroup; monthGroup: MonthGroup;
timelineManager: TimelineManager; timelineManager: TimelineManager;
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
customLayout?: Snippet<[TimelineAsset]>; customLayout?: Snippet<[Asset]>;
onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void; onSelect: ({ title, assets }: { title: string; assets: Asset[] }) => void;
onSelectAssets: (asset: TimelineAsset) => void; onSelectAssets: (asset: Asset) => void;
onSelectAssetCandidates: (asset: TimelineAsset | null) => void; onSelectAssetCandidates: (asset: Asset | null) => void;
onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void; onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void;
onThumbnailClick?: ( onThumbnailClick?: (
asset: TimelineAsset, asset: Asset,
timelineManager: TimelineManager, timelineManager: TimelineManager,
dayGroup: DayGroup, dayGroup: DayGroup,
onClick: ( onClick: (timelineManager: TimelineManager, assets: Asset[], groupTitle: string, asset: Asset) => void,
timelineManager: TimelineManager,
assets: TimelineAsset[],
groupTitle: string,
asset: TimelineAsset,
) => void,
) => void; ) => void;
} }
@ -70,12 +65,7 @@
monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150, monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150,
); );
const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100); const scaleDuration = $derived(transitionDuration === 0 ? 0 : transitionDuration + 100);
const _onClick = ( const _onClick = (timelineManager: TimelineManager, assets: Asset[], groupTitle: string, asset: Asset) => {
timelineManager: TimelineManager,
assets: TimelineAsset[],
groupTitle: string,
asset: TimelineAsset,
) => {
if (isSelectionMode || assetInteraction.selectionActive) { if (isSelectionMode || assetInteraction.selectionActive) {
assetSelectHandler(timelineManager, asset, assets, groupTitle); assetSelectHandler(timelineManager, asset, assets, groupTitle);
return; return;
@ -83,12 +73,12 @@
void navigate({ targetRoute: 'current', assetId: asset.id }); void navigate({ targetRoute: 'current', assetId: asset.id });
}; };
const handleSelectGroup = (title: string, assets: TimelineAsset[]) => onSelect({ title, assets }); const handleSelectGroup = (title: string, assets: Asset[]) => onSelect({ title, assets });
const assetSelectHandler = ( const assetSelectHandler = (
timelineManager: TimelineManager, timelineManager: TimelineManager,
asset: TimelineAsset, asset: Asset,
assetsInDayGroup: TimelineAsset[], assetsInDayGroup: Asset[],
groupTitle: string, groupTitle: string,
) => { ) => {
onSelectAssets(asset); onSelectAssets(asset);
@ -112,7 +102,7 @@
} }
}; };
const assetMouseEventHandler = (groupTitle: string, asset: TimelineAsset | null) => { const assetMouseEventHandler = (groupTitle: string, asset: Asset | null) => {
// Show multi select icon on hover on date group // Show multi select icon on hover on date group
hoveredDayGroup = groupTitle; hoveredDayGroup = groupTitle;

View file

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte'; import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import type { OnLink, OnUnlink } from '$lib/utils/actions'; import type { OnLink, OnUnlink } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { getAssetInfo, updateAsset } from '@immich/sdk'; import { getAssetInfo, updateAsset } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiLinkOff, mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js'; import { mdiLinkOff, mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js';
@ -31,14 +31,14 @@
const handleLink = async () => { const handleLink = async () => {
let [still, motion] = [...getOwnedAssets()]; let [still, motion] = [...getOwnedAssets()];
if ((still as TimelineAsset).isVideo) { if ((still as Asset).isVideo) {
[still, motion] = [motion, still]; [still, motion] = [motion, still];
} }
try { try {
loading = true; loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: motion.id } }); const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: motion.id } });
onLink({ still: toTimelineAsset(stillResponse), motion: motion as TimelineAsset }); onLink({ still: toAsset(stillResponse), motion: motion as Asset });
clearSelect(); clearSelect();
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_link_motion_video')); handleError(error, $t('errors.unable_to_link_motion_video'));
@ -60,7 +60,7 @@
loading = true; loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: null } }); const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: null } });
const motionResponse = await getAssetInfo({ ...authManager.params, id: motionId }); const motionResponse = await getAssetInfo({ ...authManager.params, id: motionId });
onUnlink({ still: toTimelineAsset(stillResponse), motion: toTimelineAsset(motionResponse) }); onUnlink({ still: toAsset(stillResponse), motion: toAsset(motionResponse) });
clearSelect(); clearSelect();
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_unlink_motion_video')); handleError(error, $t('errors.unable_to_unlink_motion_video'));

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
import type { OnStack, OnUnstack } from '$lib/utils/actions'; import type { OnStack, OnUnstack } from '$lib/utils/actions';
import { deleteStack, stackAssets } from '$lib/utils/asset-utils'; import { deleteStack, stackAssets } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { mdiImageMultipleOutline, mdiImageOffOutline } from '@mdi/js'; import { mdiImageMultipleOutline, mdiImageOffOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -35,7 +35,7 @@
} }
const unstackedAssets = await deleteStack([stack.id]); const unstackedAssets = await deleteStack([stack.id]);
if (unstackedAssets) { if (unstackedAssets) {
onUnstack?.(unstackedAssets.map((a) => toTimelineAsset(a))); onUnstack?.(unstackedAssets.map((a) => toAsset(a)));
} }
clearSelect(); clearSelect();
}; };

View file

@ -12,7 +12,7 @@
} from '$lib/components/timeline/actions/focus-actions'; } from '$lib/components/timeline/actions/focus-actions';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -32,7 +32,7 @@
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
isShowDeleteConfirmation: boolean; isShowDeleteConfirmation: boolean;
onEscape?: () => void; onEscape?: () => void;
scrollToAsset: (asset: TimelineAsset) => boolean; scrollToAsset: (asset: Asset) => boolean;
} }
let { let {

View file

@ -1,5 +1,5 @@
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { moveFocus } from '$lib/utils/focus-util'; import { moveFocus } from '$lib/utils/focus-util';
import { InvocationTracker } from '$lib/utils/invocationTracker'; import { InvocationTracker } from '$lib/utils/invocationTracker';
import { tick } from 'svelte'; import { tick } from 'svelte';
@ -21,7 +21,7 @@ export const focusPreviousAsset = () =>
const queryHTMLElement = (query: string) => document.querySelector(query) as HTMLElement; const queryHTMLElement = (query: string) => document.querySelector(query) as HTMLElement;
export const setFocusToAsset = (scrollToAsset: (asset: TimelineAsset) => boolean, asset: TimelineAsset) => { export const setFocusToAsset = (scrollToAsset: (asset: Asset) => boolean, asset: Asset) => {
const scrolled = scrollToAsset(asset); const scrolled = scrollToAsset(asset);
if (scrolled) { if (scrolled) {
const element = queryHTMLElement(`[data-thumbnail-focus-container][data-asset="${asset.id}"]`); const element = queryHTMLElement(`[data-thumbnail-focus-container][data-asset="${asset.id}"]`);
@ -30,7 +30,7 @@ export const setFocusToAsset = (scrollToAsset: (asset: TimelineAsset) => boolean
}; };
export const setFocusTo = async ( export const setFocusTo = async (
scrollToAsset: (asset: TimelineAsset) => boolean, scrollToAsset: (asset: Asset) => boolean,
store: TimelineManager, store: TimelineManager,
direction: 'earlier' | 'later', direction: 'earlier' | 'later',
interval: 'day' | 'month' | 'year' | 'asset', interval: 'day' | 'month' | 'year' | 'asset',

View file

@ -2,7 +2,7 @@
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetThumbnailUrl } from '$lib/utils';
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils'; import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { type AssetResponseDto, getAllAlbums } from '@immich/sdk'; import { type AssetResponseDto, getAllAlbums } from '@immich/sdk';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
import { mdiHeart, mdiImageMultipleOutline, mdiMagnifyPlus } from '@mdi/js'; import { mdiHeart, mdiImageMultipleOutline, mdiMagnifyPlus } from '@mdi/js';
@ -37,7 +37,7 @@
<!-- THUMBNAIL--> <!-- THUMBNAIL-->
<img <img
src={getAssetThumbnailUrl(asset.id)} src={getAssetThumbnailUrl(asset.id)}
alt={$getAltText(toTimelineAsset(asset))} alt={$getAltText(toAsset(asset))}
title={assetData} title={assetData}
class="h-60 object-cover rounded-t-xl w-full" class="h-60 object-cover rounded-t-xl w-full"
draggable="false" draggable="false"

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte'; import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import { getFileSize } from '$lib/utils/asset-utils'; import { getFileSize } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { type AssetResponseDto } from '@immich/sdk'; import { type AssetResponseDto } from '@immich/sdk';
interface Props { interface Props {
@ -22,7 +22,7 @@
title={assetData} title={assetData}
> >
<div class="relative w-full h-full overflow-hidden rounded-lg"> <div class="relative w-full h-full overflow-hidden rounded-lg">
<Thumbnail asset={toTimelineAsset(asset)} readonly onClick={() => onViewAsset(asset)} thumbnailSize={boxWidth} /> <Thumbnail asset={toAsset(asset)} readonly onClick={() => onViewAsset(asset)} thumbnailSize={boxWidth} />
{#if !!asset.libraryId} {#if !!asset.libraryId}
<div class="absolute bottom-1 end-3 px-4 py-1 rounded-xl text-xs transition-colors bg-red-500">External</div> <div class="absolute bottom-1 end-3 px-4 py-1 rounded-xl text-xs transition-colors bg-red-500">External</div>

View file

@ -6,7 +6,7 @@ import { plainDateTimeCompare } from '$lib/utils/timeline-util';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteSet } from 'svelte/reactivity';
import type { MonthGroup } from './month-group.svelte'; import type { MonthGroup } from './month-group.svelte';
import type { AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; import type { Asset, AssetOperation, Direction, MoveAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte'; import { ViewerAsset } from './viewer-asset.svelte';
export class DayGroup { export class DayGroup {
@ -82,7 +82,7 @@ export class DayGroup {
return this.viewerAssets[0]?.asset; return this.viewerAssets[0]?.asset;
} }
*assetsIterator(options: { startAsset?: TimelineAsset; direction?: Direction } = {}) { *assetsIterator(options: { startAsset?: Asset; direction?: Direction } = {}) {
const isEarlier = (options?.direction ?? 'earlier') === 'earlier'; const isEarlier = (options?.direction ?? 'earlier') === 'earlier';
let assetIndex = options?.startAsset let assetIndex = options?.startAsset
? this.viewerAssets.findIndex((viewerAsset) => viewerAsset.asset.id === options.startAsset!.id) ? this.viewerAssets.findIndex((viewerAsset) => viewerAsset.asset.id === options.startAsset!.id)

View file

@ -3,13 +3,13 @@ import { AssetOrder } from '@immich/sdk';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteSet } from 'svelte/reactivity';
import type { DayGroup } from './day-group.svelte'; import type { DayGroup } from './day-group.svelte';
import type { MonthGroup } from './month-group.svelte'; import type { MonthGroup } from './month-group.svelte';
import type { TimelineAsset } from './types'; import type { Asset } from './types';
export class GroupInsertionCache { export class GroupInsertionCache {
#lookupCache: { #lookupCache: {
[year: number]: { [month: number]: { [day: number]: DayGroup } }; [year: number]: { [month: number]: { [day: number]: DayGroup } };
} = {}; } = {};
unprocessedAssets: TimelineAsset[] = []; unprocessedAssets: Asset[] = [];
changedDayGroups = new SvelteSet<DayGroup>(); changedDayGroups = new SvelteSet<DayGroup>();
newDayGroups = new SvelteSet<DayGroup>(); newDayGroups = new SvelteSet<DayGroup>();

View file

@ -5,13 +5,13 @@ import { SvelteSet } from 'svelte/reactivity';
import { GroupInsertionCache } from '../group-insertion-cache.svelte'; import { GroupInsertionCache } from '../group-insertion-cache.svelte';
import { MonthGroup } from '../month-group.svelte'; import { MonthGroup } from '../month-group.svelte';
import type { TimelineManager } from '../timeline-manager.svelte'; import type { TimelineManager } from '../timeline-manager.svelte';
import type { AssetOperation, TimelineAsset } from '../types'; import type { Asset, AssetOperation } from '../types';
import { updateGeometry } from './layout-support.svelte'; import { updateGeometry } from './layout-support.svelte';
import { getMonthGroupByDate } from './search-support.svelte'; import { getMonthGroupByDate } from './search-support.svelte';
export function addAssetsToMonthGroups( export function addAssetsToMonthGroups(
timelineManager: TimelineManager, timelineManager: TimelineManager,
assets: TimelineAsset[], assets: Asset[],
options: { order: AssetOrder }, options: { order: AssetOrder },
) { ) {
if (assets.length === 0) { if (assets.length === 0) {
@ -30,7 +30,7 @@ export function addAssetsToMonthGroups(
timelineManager.months.push(month); timelineManager.months.push(month);
} }
month.addTimelineAsset(asset, addContext); month.addAsset(asset, addContext);
updatedMonthGroups.add(month); updatedMonthGroups.add(month);
} }
@ -70,7 +70,7 @@ export function runAssetOperation(
const changedMonthGroups = new SvelteSet<MonthGroup>(); const changedMonthGroups = new SvelteSet<MonthGroup>();
let idsToProcess = new SvelteSet(ids); let idsToProcess = new SvelteSet(ids);
const idsProcessed = new SvelteSet<string>(); const idsProcessed = new SvelteSet<string>();
const combinedMoveAssets: { asset: TimelineAsset; date: TimelineDate }[][] = []; const combinedMoveAssets: { asset: Asset; date: TimelineDate }[][] = [];
for (const month of timelineManager.months) { for (const month of timelineManager.months) {
if (idsToProcess.size > 0) { if (idsToProcess.size > 0) {
const { moveAssets, processedIds, changedGeometry } = month.runAssetOperation(idsToProcess, operation); const { moveAssets, processedIds, changedGeometry } = month.runAssetOperation(idsToProcess, operation);

View file

@ -2,14 +2,14 @@ import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timelin
import { AssetOrder } from '@immich/sdk'; import { AssetOrder } from '@immich/sdk';
import type { MonthGroup } from '../month-group.svelte'; import type { MonthGroup } from '../month-group.svelte';
import type { TimelineManager } from '../timeline-manager.svelte'; import type { TimelineManager } from '../timeline-manager.svelte';
import type { AssetDescriptor, Direction, TimelineAsset } from '../types'; import type { Asset, AssetDescriptor, Direction } from '../types';
export async function getAssetWithOffset( export async function getAssetWithOffset(
timelineManager: TimelineManager, timelineManager: TimelineManager,
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
direction: Direction, direction: Direction,
): Promise<TimelineAsset | undefined> { ): Promise<Asset | undefined> {
const { asset, monthGroup } = findMonthGroupForAsset(timelineManager, assetDescriptor.id) ?? {}; const { asset, monthGroup } = findMonthGroupForAsset(timelineManager, assetDescriptor.id) ?? {};
if (!monthGroup || !asset) { if (!monthGroup || !asset) {
return; return;
@ -51,7 +51,7 @@ export function getMonthGroupByDate(
async function getAssetByAssetOffset( async function getAssetByAssetOffset(
timelineManager: TimelineManager, timelineManager: TimelineManager,
asset: TimelineAsset, asset: Asset,
monthGroup: MonthGroup, monthGroup: MonthGroup,
direction: Direction, direction: Direction,
) { ) {
@ -70,7 +70,7 @@ async function getAssetByAssetOffset(
async function getAssetByDayOffset( async function getAssetByDayOffset(
timelineManager: TimelineManager, timelineManager: TimelineManager,
asset: TimelineAsset, asset: Asset,
monthGroup: MonthGroup, monthGroup: MonthGroup,
direction: Direction, direction: Direction,
) { ) {
@ -120,7 +120,7 @@ export async function retrieveRange(timelineManager: TimelineManager, start: Ass
[startMonthGroup, endMonthGroup] = [endMonthGroup, startMonthGroup]; [startMonthGroup, endMonthGroup] = [endMonthGroup, startMonthGroup];
} }
const range: TimelineAsset[] = []; const range: Asset[] = [];
const startDayGroup = startMonthGroup.findDayGroupForAsset(startAsset); const startDayGroup = startMonthGroup.findDayGroupForAsset(startAsset);
for await (const targetAsset of timelineManager.assetsIterator({ for await (const targetAsset of timelineManager.assetsIterator({
startMonthGroup, startMonthGroup,

View file

@ -1,7 +1,7 @@
import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { PendingChange, TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset, PendingChange } from '$lib/managers/timeline-manager/types';
import { websocketEvents } from '$lib/stores/websocket'; import { websocketEvents } from '$lib/stores/websocket';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { throttle } from 'lodash-es'; import { throttle } from 'lodash-es';
import type { Unsubscriber } from 'svelte/store'; import type { Unsubscriber } from 'svelte/store';
@ -31,11 +31,11 @@ export class WebsocketSupport {
connectWebsocketEvents() { connectWebsocketEvents() {
this.#unsubscribers.push( this.#unsubscribers.push(
websocketEvents.on('on_upload_success', (asset) => websocketEvents.on('on_upload_success', (asset) =>
this.#addPendingChanges({ type: 'add', values: [toTimelineAsset(asset)] }), this.#addPendingChanges({ type: 'add', values: [toAsset(asset)] }),
), ),
websocketEvents.on('on_asset_trash', (ids) => this.#addPendingChanges({ type: 'trash', values: ids })), websocketEvents.on('on_asset_trash', (ids) => this.#addPendingChanges({ type: 'trash', values: ids })),
websocketEvents.on('on_asset_update', (asset) => websocketEvents.on('on_asset_update', (asset) =>
this.#addPendingChanges({ type: 'update', values: [toTimelineAsset(asset)] }), this.#addPendingChanges({ type: 'update', values: [toAsset(asset)] }),
), ),
websocketEvents.on('on_asset_delete', (id: string) => this.#addPendingChanges({ type: 'delete', values: [id] })), websocketEvents.on('on_asset_delete', (id: string) => this.#addPendingChanges({ type: 'delete', values: [id] })),
); );
@ -55,8 +55,8 @@ export class WebsocketSupport {
#getPendingChangeBatches() { #getPendingChangeBatches() {
const batch: { const batch: {
add: TimelineAsset[]; add: Asset[];
update: TimelineAsset[]; update: Asset[];
remove: string[]; remove: string[];
} = { } = {
add: [], add: [],

View file

@ -21,7 +21,7 @@ import { SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte'; import { DayGroup } from './day-group.svelte';
import { GroupInsertionCache } from './group-insertion-cache.svelte'; import { GroupInsertionCache } from './group-insertion-cache.svelte';
import type { TimelineManager } from './timeline-manager.svelte'; import type { TimelineManager } from './timeline-manager.svelte';
import type { AssetDescriptor, AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; import type { Asset, AssetDescriptor, AssetOperation, Direction, MoveAsset } from './types';
import { ViewerAsset } from './viewer-asset.svelte'; import { ViewerAsset } from './viewer-asset.svelte';
export class MonthGroup { export class MonthGroup {
@ -101,7 +101,7 @@ export class MonthGroup {
getAssets() { getAssets() {
// eslint-disable-next-line unicorn/no-array-reduce // eslint-disable-next-line unicorn/no-array-reduce
return this.dayGroups.reduce((accumulator: TimelineAsset[], g: DayGroup) => accumulator.concat(g.getAssets()), []); return this.dayGroups.reduce((accumulator: Asset[], g: DayGroup) => accumulator.concat(g.getAssets()), []);
} }
sortDayGroups() { sortDayGroups() {
@ -161,7 +161,7 @@ export class MonthGroup {
bucketAssets.localOffsetHours[i], bucketAssets.localOffsetHours[i],
); );
const timelineAsset: TimelineAsset = { const timelineAsset: Asset = {
city: bucketAssets.city[i], city: bucketAssets.city[i],
country: bucketAssets.country[i], country: bucketAssets.country[i],
duration: bucketAssets.duration[i], duration: bucketAssets.duration[i],
@ -192,7 +192,7 @@ export class MonthGroup {
timelineAsset.latitude = bucketAssets.latitude?.[i]; timelineAsset.latitude = bucketAssets.latitude?.[i];
timelineAsset.longitude = bucketAssets.longitude?.[i]; timelineAsset.longitude = bucketAssets.longitude?.[i];
} }
this.addTimelineAsset(timelineAsset, addContext); this.addAsset(timelineAsset, addContext);
} }
for (const group of addContext.existingDayGroups) { for (const group of addContext.existingDayGroups) {
@ -208,7 +208,7 @@ export class MonthGroup {
return addContext.unprocessedAssets; return addContext.unprocessedAssets;
} }
addTimelineAsset(timelineAsset: TimelineAsset, addContext: GroupInsertionCache) { addAsset(timelineAsset: Asset, addContext: GroupInsertionCache) {
const { localDateTime } = timelineAsset; const { localDateTime } = timelineAsset;
const { year, month } = this.yearMonth; const { year, month } = this.yearMonth;
@ -294,7 +294,7 @@ export class MonthGroup {
handleError(error, _$t('errors.failed_to_load_assets')); handleError(error, _$t('errors.failed_to_load_assets'));
} }
findDayGroupForAsset(asset: TimelineAsset) { findDayGroupForAsset(asset: Asset) {
for (const group of this.dayGroups) { for (const group of this.dayGroups) {
if (group.viewerAssets.some((viewerAsset) => viewerAsset.id === asset.id)) { if (group.viewerAssets.some((viewerAsset) => viewerAsset.id === asset.id)) {
return group; return group;
@ -321,7 +321,7 @@ export class MonthGroup {
return -1; return -1;
} }
*assetsIterator(options?: { startDayGroup?: DayGroup; startAsset?: TimelineAsset; direction?: Direction }) { *assetsIterator(options?: { startDayGroup?: DayGroup; startAsset?: Asset; direction?: Direction }) {
const direction = options?.direction ?? 'earlier'; const direction = options?.direction ?? 'earlier';
let { startAsset } = options ?? {}; let { startAsset } = options ?? {};
const isEarlier = direction === 'earlier'; const isEarlier = direction === 'earlier';

View file

@ -5,7 +5,7 @@ import { fromISODateTimeUTCToObject } from '$lib/utils/timeline-util';
import { type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; import { type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory'; import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
import { TimelineManager } from './timeline-manager.svelte'; import { TimelineManager } from './timeline-manager.svelte';
import type { TimelineAsset } from './types'; import type { Asset } from './types';
async function getAssets(timelineManager: TimelineManager) { async function getAssets(timelineManager: TimelineManager) {
const assets = []; const assets = [];
@ -15,7 +15,7 @@ async function getAssets(timelineManager: TimelineManager) {
return assets; return assets;
} }
function deriveLocalDateTimeFromFileCreatedAt(arg: TimelineAsset): TimelineAsset { function deriveLocalDateTimeFromFileCreatedAt(arg: Asset): Asset {
return { return {
...arg, ...arg,
localDateTime: arg.fileCreatedAt, localDateTime: arg.fileCreatedAt,
@ -29,7 +29,7 @@ describe('TimelineManager', () => {
describe('init', () => { describe('init', () => {
let timelineManager: TimelineManager; let timelineManager: TimelineManager;
const bucketAssets: Record<string, TimelineAsset[]> = { const bucketAssets: Record<string, Asset[]> = {
'2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) => '2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) =>
deriveLocalDateTimeFromFileCreatedAt({ deriveLocalDateTimeFromFileCreatedAt({
...asset, ...asset,
@ -94,7 +94,7 @@ describe('TimelineManager', () => {
describe('loadMonthGroup', () => { describe('loadMonthGroup', () => {
let timelineManager: TimelineManager; let timelineManager: TimelineManager;
const bucketAssets: Record<string, TimelineAsset[]> = { const bucketAssets: Record<string, Asset[]> = {
'2024-01-03T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) => '2024-01-03T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) =>
deriveLocalDateTimeFromFileCreatedAt({ deriveLocalDateTimeFromFileCreatedAt({
...asset, ...asset,
@ -436,7 +436,7 @@ describe('TimelineManager', () => {
describe('getLaterAsset', () => { describe('getLaterAsset', () => {
let timelineManager: TimelineManager; let timelineManager: TimelineManager;
const bucketAssets: Record<string, TimelineAsset[]> = { const bucketAssets: Record<string, Asset[]> = {
'2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) => '2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) =>
deriveLocalDateTimeFromFileCreatedAt({ deriveLocalDateTimeFromFileCreatedAt({
...asset, ...asset,
@ -583,7 +583,7 @@ describe('TimelineManager', () => {
describe('getRandomAsset', () => { describe('getRandomAsset', () => {
let timelineManager: TimelineManager; let timelineManager: TimelineManager;
const bucketAssets: Record<string, TimelineAsset[]> = { const bucketAssets: Record<string, Asset[]> = {
'2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) => '2024-03-01T00:00:00.000Z': timelineAssetFactory.buildList(1).map((asset) =>
deriveLocalDateTimeFromFileCreatedAt({ deriveLocalDateTimeFromFileCreatedAt({
...asset, ...asset,

View file

@ -3,7 +3,7 @@ import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { CancellableTask } from '$lib/utils/cancellable-task'; import { CancellableTask } from '$lib/utils/cancellable-task';
import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util'; import { toAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util';
import { clamp, debounce, isEqual } from 'lodash-es'; import { clamp, debounce, isEqual } from 'lodash-es';
import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
@ -27,11 +27,11 @@ import { DayGroup } from './day-group.svelte';
import { isMismatched, updateObject } from './internal/utils.svelte'; import { isMismatched, updateObject } from './internal/utils.svelte';
import { MonthGroup } from './month-group.svelte'; import { MonthGroup } from './month-group.svelte';
import type { import type {
Asset,
AssetDescriptor, AssetDescriptor,
AssetOperation, AssetOperation,
Direction, Direction,
ScrubberMonth, ScrubberMonth,
TimelineAsset,
TimelineManagerLayoutOptions, TimelineManagerLayoutOptions,
TimelineManagerOptions, TimelineManagerOptions,
Viewport, Viewport,
@ -192,7 +192,7 @@ export class TimelineManager {
async *assetsIterator(options?: { async *assetsIterator(options?: {
startMonthGroup?: MonthGroup; startMonthGroup?: MonthGroup;
startDayGroup?: DayGroup; startDayGroup?: DayGroup;
startAsset?: TimelineAsset; startAsset?: Asset;
direction?: Direction; direction?: Direction;
}) { }) {
const direction = options?.direction ?? 'earlier'; const direction = options?.direction ?? 'earlier';
@ -409,7 +409,7 @@ export class TimelineManager {
} }
} }
addAssets(assets: TimelineAsset[]) { addAssets(assets: Asset[]) {
const assetsToUpdate = assets.filter((asset) => !this.isExcluded(asset)); const assetsToUpdate = assets.filter((asset) => !this.isExcluded(asset));
const notUpdated = this.updateAssets(assetsToUpdate); const notUpdated = this.updateAssets(assetsToUpdate);
addAssetsToMonthGroups(this, [...notUpdated], { order: this.#options.order ?? AssetOrder.Desc }); addAssetsToMonthGroups(this, [...notUpdated], { order: this.#options.order ?? AssetOrder.Desc });
@ -430,7 +430,7 @@ export class TimelineManager {
return; return;
} }
const asset = toTimelineAsset(response); const asset = toAsset(response);
if (!asset || this.isExcluded(asset)) { if (!asset || this.isExcluded(asset)) {
return; return;
} }
@ -454,7 +454,7 @@ export class TimelineManager {
// note: the `index` input is expected to be in the range [0, assetCount). This // note: the `index` input is expected to be in the range [0, assetCount). This
// value can be passed to make the method deterministic, which is mainly useful // value can be passed to make the method deterministic, which is mainly useful
// for testing. // for testing.
async getRandomAsset(index?: number): Promise<TimelineAsset | undefined> { async getRandomAsset(index?: number): Promise<Asset | undefined> {
const randomAssetIndex = index ?? Math.floor(Math.random() * this.assetCount); const randomAssetIndex = index ?? Math.floor(Math.random() * this.assetCount);
let accumulatedCount = 0; let accumulatedCount = 0;
@ -493,8 +493,8 @@ export class TimelineManager {
runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc }); runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc });
} }
updateAssets(assets: TimelineAsset[]) { updateAssets(assets: Asset[]) {
const lookup = new SvelteMap<string, TimelineAsset>(assets.map((asset) => [asset.id, asset])); const lookup = new SvelteMap<string, Asset>(assets.map((asset) => [asset.id, asset]));
const { unprocessedIds } = runAssetOperation( const { unprocessedIds } = runAssetOperation(
this, this,
new SvelteSet(lookup.keys()), new SvelteSet(lookup.keys()),
@ -504,7 +504,7 @@ export class TimelineManager {
}, },
{ order: this.#options.order ?? AssetOrder.Desc }, { order: this.#options.order ?? AssetOrder.Desc },
); );
const result: TimelineAsset[] = []; const result: Asset[] = [];
for (const id of unprocessedIds.values()) { for (const id of unprocessedIds.values()) {
result.push(lookup.get(id)!); result.push(lookup.get(id)!);
} }
@ -530,21 +530,21 @@ export class TimelineManager {
this.updateIntersections(); this.updateIntersections();
} }
getFirstAsset(): TimelineAsset | undefined { getFirstAsset(): Asset | undefined {
return this.months[0]?.getFirstAsset(); return this.months[0]?.getFirstAsset();
} }
async getLaterAsset( async getLaterAsset(
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
): Promise<TimelineAsset | undefined> { ): Promise<Asset | undefined> {
return await getAssetWithOffset(this, assetDescriptor, interval, 'later'); return await getAssetWithOffset(this, assetDescriptor, interval, 'later');
} }
async getEarlierAsset( async getEarlierAsset(
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
): Promise<TimelineAsset | undefined> { ): Promise<Asset | undefined> {
return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier'); return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier');
} }
@ -567,7 +567,7 @@ export class TimelineManager {
return retrieveRangeUtil(this, start, end); return retrieveRangeUtil(this, start, end);
} }
isExcluded(asset: TimelineAsset) { isExcluded(asset: Asset) {
return ( return (
isMismatched(this.#options.visibility, asset.visibility) || isMismatched(this.#options.visibility, asset.visibility) ||
isMismatched(this.#options.isFavorite, asset.isFavorite) || isMismatched(this.#options.isFavorite, asset.isFavorite) ||

View file

@ -12,7 +12,7 @@ export type AssetDescriptor = { id: string };
export type Direction = 'earlier' | 'later'; export type Direction = 'earlier' | 'later';
export type TimelineAsset = { export type Asset = {
id: string; id: string;
ownerId: string; ownerId: string;
ratio: number; ratio: number;
@ -35,9 +35,9 @@ export type TimelineAsset = {
longitude?: number | null; longitude?: number | null;
}; };
export type AssetOperation = (asset: TimelineAsset) => { remove: boolean }; export type AssetOperation = (asset: Asset) => { remove: boolean };
export type MoveAsset = { asset: TimelineAsset; date: TimelineDate }; export type MoveAsset = { asset: Asset; date: TimelineDate };
export interface Viewport { export interface Viewport {
width: number; width: number;
@ -51,12 +51,12 @@ export type ViewportXY = Viewport & {
export interface AddAsset { export interface AddAsset {
type: 'add'; type: 'add';
values: TimelineAsset[]; values: Asset[];
} }
export interface UpdateAsset { export interface UpdateAsset {
type: 'update'; type: 'update';
values: TimelineAsset[]; values: Asset[];
} }
export interface DeleteAsset { export interface DeleteAsset {

View file

@ -1,4 +1,4 @@
import type { TimelineAsset } from './types'; import type { Asset } from './types';
export const assetSnapshot = (asset: TimelineAsset): TimelineAsset => $state.snapshot(asset); export const assetSnapshot = (asset: Asset): Asset => $state.snapshot(asset);
export const assetsSnapshot = (assets: TimelineAsset[]) => assets.map((asset) => $state.snapshot(asset)); export const assetsSnapshot = (assets: Asset[]) => assets.map((asset) => $state.snapshot(asset));

View file

@ -2,7 +2,7 @@ import type { CommonPosition } from '$lib/utils/layout-utils';
import type { DayGroup } from './day-group.svelte'; import type { DayGroup } from './day-group.svelte';
import { calculateViewerAssetIntersecting } from './internal/intersection-support.svelte'; import { calculateViewerAssetIntersecting } from './internal/intersection-support.svelte';
import type { TimelineAsset } from './types'; import type { Asset } from './types';
export class ViewerAsset { export class ViewerAsset {
readonly #group: DayGroup; readonly #group: DayGroup;
@ -19,10 +19,10 @@ export class ViewerAsset {
}); });
position: CommonPosition | undefined = $state(); position: CommonPosition | undefined = $state();
asset: TimelineAsset = <TimelineAsset>$state(); asset: Asset = <Asset>$state();
id: string = $derived(this.asset.id); id: string = $derived(this.asset.id);
constructor(group: DayGroup, asset: TimelineAsset) { constructor(group: DayGroup, asset: Asset) {
this.#group = group; this.#group = group;
this.asset = asset; this.asset = asset;
} }

View file

@ -1,20 +1,20 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { AssetVisibility, type UserAdminResponseDto } from '@immich/sdk'; import { AssetVisibility, type UserAdminResponseDto } from '@immich/sdk';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteSet } from 'svelte/reactivity';
import { fromStore } from 'svelte/store'; import { fromStore } from 'svelte/store';
export class AssetInteraction { export class AssetInteraction {
selectedAssets = $state<TimelineAsset[]>([]); selectedAssets = $state<Asset[]>([]);
hasSelectedAsset(assetId: string) { hasSelectedAsset(assetId: string) {
return this.selectedAssets.some((asset) => asset.id === assetId); return this.selectedAssets.some((asset) => asset.id === assetId);
} }
selectedGroup = new SvelteSet<string>(); selectedGroup = new SvelteSet<string>();
assetSelectionCandidates = $state<TimelineAsset[]>([]); assetSelectionCandidates = $state<Asset[]>([]);
hasSelectionCandidate(assetId: string) { hasSelectionCandidate(assetId: string) {
return this.assetSelectionCandidates.some((asset) => asset.id === assetId); return this.assetSelectionCandidates.some((asset) => asset.id === assetId);
} }
assetSelectionStart = $state<TimelineAsset | null>(null); assetSelectionStart = $state<Asset | null>(null);
selectionActive = $derived(this.selectedAssets.length > 0); selectionActive = $derived(this.selectedAssets.length > 0);
private user = fromStore<UserAdminResponseDto | undefined>(user); private user = fromStore<UserAdminResponseDto | undefined>(user);
@ -25,13 +25,13 @@ export class AssetInteraction {
isAllFavorite = $derived(this.selectedAssets.every((asset) => asset.isFavorite)); isAllFavorite = $derived(this.selectedAssets.every((asset) => asset.isFavorite));
isAllUserOwned = $derived(this.selectedAssets.every((asset) => asset.ownerId === this.userId)); isAllUserOwned = $derived(this.selectedAssets.every((asset) => asset.ownerId === this.userId));
selectAsset(asset: TimelineAsset) { selectAsset(asset: Asset) {
if (!this.hasSelectedAsset(asset.id)) { if (!this.hasSelectedAsset(asset.id)) {
this.selectedAssets.push(asset); this.selectedAssets.push(asset);
} }
} }
selectAssets(assets: TimelineAsset[]) { selectAssets(assets: Asset[]) {
for (const asset of assets) { for (const asset of assets) {
this.selectAsset(asset); this.selectAsset(asset);
} }
@ -52,11 +52,11 @@ export class AssetInteraction {
this.selectedGroup.delete(group); this.selectedGroup.delete(group);
} }
setAssetSelectionStart(asset: TimelineAsset | null) { setAssetSelectionStart(asset: Asset | null) {
this.assetSelectionStart = asset; this.assetSelectionStart = asset;
} }
setAssetSelectionCandidates(assets: TimelineAsset[]) { setAssetSelectionCandidates(assets: Asset[]) {
this.assetSelectionCandidates = assets; this.assetSelectionCandidates = assets;
} }

View file

@ -1,5 +1,5 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { type AssetGridRouteSearchParams } from '$lib/utils/navigation'; import { type AssetGridRouteSearchParams } from '$lib/utils/navigation';
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk'; import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
@ -7,12 +7,12 @@ import { readonly, writable } from 'svelte/store';
function createAssetViewingStore() { function createAssetViewingStore() {
const viewingAssetStoreState = writable<AssetResponseDto>(); const viewingAssetStoreState = writable<AssetResponseDto>();
const preloadAssets = writable<TimelineAsset[]>([]); const preloadAssets = writable<Asset[]>([]);
const viewState = writable<boolean>(false); const viewState = writable<boolean>(false);
const viewingAssetMutex = new Mutex(); const viewingAssetMutex = new Mutex();
const gridScrollTarget = writable<AssetGridRouteSearchParams | null | undefined>(); const gridScrollTarget = writable<AssetGridRouteSearchParams | null | undefined>();
const setAsset = (asset: AssetResponseDto, assetsToPreload: TimelineAsset[] = []) => { const setAsset = (asset: AssetResponseDto, assetsToPreload: Asset[] = []) => {
preloadAssets.set(assetsToPreload); preloadAssets.set(assetsToPreload);
viewingAssetStoreState.set(asset); viewingAssetStoreState.set(asset);
viewState.set(true); viewState.set(true);

View file

@ -1,7 +1,7 @@
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { asLocalTimeISO } from '$lib/utils/date-time'; import { asLocalTimeISO } from '$lib/utils/date-time';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { deleteMemory, type MemoryResponseDto, removeMemoryAssets, searchMemories, updateMemory } from '@immich/sdk'; import { deleteMemory, type MemoryResponseDto, removeMemoryAssets, searchMemories, updateMemory } from '@immich/sdk';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
@ -12,7 +12,7 @@ type MemoryIndex = {
export type MemoryAsset = MemoryIndex & { export type MemoryAsset = MemoryIndex & {
memory: MemoryResponseDto; memory: MemoryResponseDto;
asset: TimelineAsset; asset: Asset;
previousMemory?: MemoryResponseDto; previousMemory?: MemoryResponseDto;
previous?: MemoryAsset; previous?: MemoryAsset;
next?: MemoryAsset; next?: MemoryAsset;
@ -36,7 +36,7 @@ class MemoryStoreSvelte {
memoryIndex, memoryIndex,
previousMemory: this.memories[memoryIndex - 1], previousMemory: this.memories[memoryIndex - 1],
nextMemory: this.memories[memoryIndex + 1], nextMemory: this.memories[memoryIndex + 1],
asset: toTimelineAsset(asset), asset: toAsset(asset),
assetIndex, assetIndex,
previous, previous,
}; };

View file

@ -1,6 +1,6 @@
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import type { StackResponse } from '$lib/utils/asset-utils'; import type { StackResponse } from '$lib/utils/asset-utils';
import { AssetVisibility, deleteAssets as deleteBulk, restoreAssets } from '@immich/sdk'; import { AssetVisibility, deleteAssets as deleteBulk, restoreAssets } from '@immich/sdk';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -8,21 +8,21 @@ import { get } from 'svelte/store';
import { handleError } from './handle-error'; import { handleError } from './handle-error';
export type OnDelete = (assetIds: string[]) => void; export type OnDelete = (assetIds: string[]) => void;
export type OnUndoDelete = (assets: TimelineAsset[]) => void; export type OnUndoDelete = (assets: Asset[]) => void;
export type OnRestore = (ids: string[]) => void; export type OnRestore = (ids: string[]) => void;
export type OnLink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void; export type OnLink = (assets: { still: Asset; motion: Asset }) => void;
export type OnUnlink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void; export type OnUnlink = (assets: { still: Asset; motion: Asset }) => void;
export type OnAddToAlbum = (ids: string[], albumId: string) => void; export type OnAddToAlbum = (ids: string[], albumId: string) => void;
export type OnArchive = (ids: string[], visibility: AssetVisibility) => void; export type OnArchive = (ids: string[], visibility: AssetVisibility) => void;
export type OnFavorite = (ids: string[], favorite: boolean) => void; export type OnFavorite = (ids: string[], favorite: boolean) => void;
export type OnStack = (result: StackResponse) => void; export type OnStack = (result: StackResponse) => void;
export type OnUnstack = (assets: TimelineAsset[]) => void; export type OnUnstack = (assets: Asset[]) => void;
export type OnSetVisibility = (ids: string[]) => void; export type OnSetVisibility = (ids: string[]) => void;
export const deleteAssets = async ( export const deleteAssets = async (
force: boolean, force: boolean,
onAssetDelete: OnDelete, onAssetDelete: OnDelete,
assets: TimelineAsset[], assets: Asset[],
onUndoDelete: OnUndoDelete | undefined = undefined, onUndoDelete: OnUndoDelete | undefined = undefined,
) => { ) => {
const $t = get(t); const $t = get(t);
@ -47,7 +47,7 @@ export const deleteAssets = async (
} }
}; };
const undoDeleteAssets = async (onUndoDelete: OnUndoDelete, assets: TimelineAsset[]) => { const undoDeleteAssets = async (onUndoDelete: OnUndoDelete, assets: Asset[]) => {
const $t = get(t); const $t = get(t);
try { try {
const ids = assets.map((a) => a.id); const ids = assets.map((a) => a.id);
@ -89,7 +89,7 @@ export function updateStackedAssetInTimeline(timelineManager: TimelineManager, {
* @param timelineManager - The timeline manager to update. * @param timelineManager - The timeline manager to update.
* @param assets - The array of asset response DTOs to update in the timeline manager. * @param assets - The array of asset response DTOs to update in the timeline manager.
*/ */
export function updateUnstackedAssetInTimeline(timelineManager: TimelineManager, assets: TimelineAsset[]) { export function updateUnstackedAssetInTimeline(timelineManager: TimelineManager, assets: Asset[]) {
timelineManager.updateAssetOperation( timelineManager.updateAssetOperation(
assets.map((asset) => asset.id), assets.map((asset) => asset.id),
(asset) => { (asset) => {

View file

@ -4,7 +4,7 @@ import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadManager } from '$lib/managers/download-manager.svelte'; import { downloadManager } from '$lib/managers/download-manager.svelte';
import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte'; import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
@ -405,7 +405,7 @@ export const getAssetType = (type: AssetTypeEnum) => {
} }
}; };
export const getSelectedAssets = (assets: TimelineAsset[], user: UserResponseDto | null): string[] => { export const getSelectedAssets = (assets: Asset[], user: UserResponseDto | null): string[] => {
const ids = [...assets].filter((a) => user && a.ownerId === user.id).map((a) => a.id); const ids = [...assets].filter((a) => user && a.ownerId === user.id).map((a) => a.id);
const numberOfIssues = [...assets].filter((a) => user && a.ownerId !== user.id).length; const numberOfIssues = [...assets].filter((a) => user && a.ownerId !== user.id).length;

View file

@ -2,9 +2,9 @@
// note: it's important that this is not imported in more than one file due to https://github.com/sveltejs/kit/issues/7805 // note: it's important that this is not imported in more than one file due to https://github.com/sveltejs/kit/issues/7805
// import { JustifiedLayout, type LayoutOptions } from '@immich/justified-layout-wasm'; // import { JustifiedLayout, type LayoutOptions } from '@immich/justified-layout-wasm';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { getAssetRatio } from '$lib/utils/asset-utils'; import { getAssetRatio } from '$lib/utils/asset-utils';
import { isTimelineAsset } from '$lib/utils/timeline-util'; import { isAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk'; import type { AssetResponseDto } from '@immich/sdk';
import createJustifiedLayout from 'justified-layout'; import createJustifiedLayout from 'justified-layout';
@ -29,7 +29,7 @@ export type CommonLayoutOptions = {
}; };
export function getJustifiedLayoutFromAssets( export function getJustifiedLayoutFromAssets(
assets: (TimelineAsset | AssetResponseDto)[], assets: (Asset | AssetResponseDto)[],
options: CommonLayoutOptions, options: CommonLayoutOptions,
): CommonJustifiedLayout { ): CommonJustifiedLayout {
// if (useWasm) { // if (useWasm) {
@ -90,7 +90,7 @@ class Adapter {
} }
} }
export function justifiedLayout(assets: (TimelineAsset | AssetResponseDto)[], options: CommonLayoutOptions) { export function justifiedLayout(assets: (Asset | AssetResponseDto)[], options: CommonLayoutOptions) {
const adapter = { const adapter = {
targetRowHeight: options.rowHeight, targetRowHeight: options.rowHeight,
containerWidth: options.rowWidth, containerWidth: options.rowWidth,
@ -100,7 +100,7 @@ export function justifiedLayout(assets: (TimelineAsset | AssetResponseDto)[], op
}; };
const result = createJustifiedLayout( const result = createJustifiedLayout(
assets.map((asset) => (isTimelineAsset(asset) ? asset.ratio : getAssetRatio(asset))), assets.map((asset) => (isAsset(asset) ? asset.ratio : getAssetRatio(asset))),
adapter, adapter,
); );
return new Adapter(result); return new Adapter(result);

View file

@ -1,4 +1,4 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetVisibility } from '@immich/sdk'; import { AssetVisibility } from '@immich/sdk';
import { init, register, waitLocale } from 'svelte-i18n'; import { init, register, waitLocale } from 'svelte-i18n';
@ -57,7 +57,7 @@ describe('getAltText', () => {
expected: string; expected: string;
}) => { }) => {
const testDate = new Date('2024-01-01T12:00:00.000Z'); const testDate = new Date('2024-01-01T12:00:00.000Z');
const asset: TimelineAsset = { const asset: Asset = {
id: 'test-id', id: 'test-id',
ownerId: 'test-owner', ownerId: 'test-owner',
ratio: 1, ratio: 1,

View file

@ -1,4 +1,4 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { fromTimelinePlainDateTime } from '$lib/utils/timeline-util'; import { fromTimelinePlainDateTime } from '$lib/utils/timeline-util';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -39,7 +39,7 @@ export function getThumbnailSize(assetCount: number, viewWidth: number): number
} }
export const getAltText = derived(t, ($t) => { export const getAltText = derived(t, ($t) => {
return (asset: TimelineAsset) => { return (asset: Asset) => {
const date = fromTimelinePlainDateTime(asset.localDateTime).toJSDate().toLocaleString(get(locale), { const date = fromTimelinePlainDateTime(asset.localDateTime).toJSDate().toLocaleString(get(locale), {
dateStyle: 'long', dateStyle: 'long',
timeZone: 'UTC', timeZone: 'UTC',

View file

@ -1,4 +1,4 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getAssetRatio } from '$lib/utils/asset-utils'; import { getAssetRatio } from '$lib/utils/asset-utils';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
@ -160,8 +160,8 @@ export const getDateTimeOffsetLocaleString = (date: DateTime, opts?: LocaleOptio
opts, opts,
); );
export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): TimelineAsset => { export const toAsset = (unknownAsset: AssetResponseDto | Asset): Asset => {
if (isTimelineAsset(unknownAsset)) { if (isAsset(unknownAsset)) {
return unknownAsset; return unknownAsset;
} }
const assetResponse = unknownAsset; const assetResponse = unknownAsset;
@ -198,8 +198,8 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
}; };
}; };
export const isTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): unknownAsset is TimelineAsset => export const isAsset = (unknownAsset: AssetResponseDto | Asset): unknownAsset is Asset =>
(unknownAsset as TimelineAsset).ratio !== undefined; (unknownAsset as Asset).ratio !== undefined;
export const plainDateTimeCompare = (ascending: boolean, a: TimelineDateTime, b: TimelineDateTime) => { export const plainDateTimeCompare = (ascending: boolean, a: TimelineDateTime, b: TimelineDateTime) => {
const [aDateTime, bDateTime] = ascending ? [a, b] : [b, a]; const [aDateTime, bDateTime] = ascending ? [a, b] : [b, a];

View file

@ -34,7 +34,7 @@
import { AlbumPageViewMode, AppRoute } from '$lib/constants'; import { AlbumPageViewMode, AppRoute } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte'; import { activityManager } from '$lib/managers/activity-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte'; import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte'; import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte'; import AlbumUsersModal from '$lib/modals/AlbumUsersModal.svelte';
@ -272,7 +272,7 @@
await refreshAlbum(); await refreshAlbum();
}; };
const handleUndoRemoveAssets = async (assets: TimelineAsset[]) => { const handleUndoRemoveAssets = async (assets: Asset[]) => {
timelineManager.addAssets(assets); timelineManager.addAssets(assets);
await refreshAlbum(); await refreshAlbum();
}; };

View file

@ -26,7 +26,7 @@
import { foldersStore } from '$lib/stores/folders.svelte'; import { foldersStore } from '$lib/stores/folders.svelte';
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { cancelMultiselect } from '$lib/utils/asset-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { joinPaths } from '$lib/utils/tree-utils'; import { joinPaths } from '$lib/utils/tree-utils';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js'; import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
@ -72,7 +72,7 @@
return; return;
} }
assetInteraction.selectAssets(data.pathAssets.map((asset) => toTimelineAsset(asset))); assetInteraction.selectAssets(data.pathAssets.map((asset) => toAsset(asset)));
} }
</script> </script>

View file

@ -31,7 +31,7 @@
import Timeline from '$lib/components/timeline/Timeline.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants'; import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte'; import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte'; import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
@ -204,7 +204,7 @@
data = { ...data, person }; data = { ...data, person };
}; };
const handleSelectFeaturePhoto = async (asset: TimelineAsset) => { const handleSelectFeaturePhoto = async (asset: Asset) => {
if (viewMode !== PersonPageViewMode.SELECT_PERSON) { if (viewMode !== PersonPageViewMode.SELECT_PERSON) {
return; return;
} }
@ -354,7 +354,7 @@
await updateAssetCount(); await updateAssetCount();
}; };
const handleUndoDeleteAssets = async (assets: TimelineAsset[]) => { const handleUndoDeleteAssets = async (assets: Asset[]) => {
timelineManager.addAssets(assets); timelineManager.addAssets(assets);
await updateAssetCount(); await updateAssetCount();
}; };

View file

@ -22,7 +22,7 @@
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants'; import { AppRoute, QueryParameter } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import type { Asset, Viewport } from '$lib/managers/timeline-manager/types';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { lang, locale } from '$lib/stores/preferences.store'; import { lang, locale } from '$lib/stores/preferences.store';
@ -33,7 +33,7 @@
import { parseUtcDate } from '$lib/utils/date-time'; import { parseUtcDate } from '$lib/utils/date-time';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation'; import { isAlbumsRoute, isPeopleRoute } from '$lib/utils/navigation';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { import {
type AlbumResponseDto, type AlbumResponseDto,
getPerson, getPerson,
@ -59,7 +59,7 @@
let nextPage = $state(1); let nextPage = $state(1);
let searchResultAlbums: AlbumResponseDto[] = $state([]); let searchResultAlbums: AlbumResponseDto[] = $state([]);
let searchResultAssets: TimelineAsset[] = $state([]); let searchResultAssets: Asset[] = $state([]);
let isLoading = $state(true); let isLoading = $state(true);
let scrollY = $state(0); let scrollY = $state(0);
let scrollYHistory = 0; let scrollYHistory = 0;
@ -125,7 +125,7 @@
const onAssetDelete = (assetIds: string[]) => { const onAssetDelete = (assetIds: string[]) => {
const assetIdSet = new Set(assetIds); const assetIdSet = new Set(assetIds);
searchResultAssets = searchResultAssets.filter((asset: TimelineAsset) => !assetIdSet.has(asset.id)); searchResultAssets = searchResultAssets.filter((asset: Asset) => !assetIdSet.has(asset.id));
}; };
const handleSetVisibility = (assetIds: string[]) => { const handleSetVisibility = (assetIds: string[]) => {
@ -167,7 +167,7 @@
: await searchAssets({ metadataSearchDto: searchDto }); : await searchAssets({ metadataSearchDto: searchDto });
searchResultAlbums.push(...albums.items); searchResultAlbums.push(...albums.items);
searchResultAssets.push(...assets.items.map((asset) => toTimelineAsset(asset))); searchResultAssets.push(...assets.items.map((asset) => toAsset(asset)));
nextPage = Number(assets.nextPage) || 0; nextPage = Number(assets.nextPage) || 0;
} catch (error) { } catch (error) {

View file

@ -7,12 +7,12 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte'; import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { cancelMultiselect } from '$lib/utils/asset-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils';
import { setQueryValue } from '$lib/utils/navigation'; import { setQueryValue } from '$lib/utils/navigation';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toAsset } from '$lib/utils/timeline-util';
import { AssetVisibility, getAssetInfo, updateAssets } from '@immich/sdk'; import { AssetVisibility, getAssetInfo, updateAssets } from '@immich/sdk';
import { Button, LoadingSpinner, modalManager, Text } from '@immich/ui'; import { Button, LoadingSpinner, modalManager, Text } from '@immich/ui';
import { mdiMapMarkerMultipleOutline, mdiPencilOutline, mdiSelectRemove } from '@mdi/js'; import { mdiMapMarkerMultipleOutline, mdiPencilOutline, mdiSelectRemove } from '@mdi/js';
@ -59,7 +59,7 @@
const updatedAssets = await Promise.all( const updatedAssets = await Promise.all(
assetInteraction.selectedAssets.map(async (asset) => { assetInteraction.selectedAssets.map(async (asset) => {
const updatedAsset = await getAssetInfo({ ...authManager.params, id: asset.id }); const updatedAsset = await getAssetInfo({ ...authManager.params, id: asset.id });
return toTimelineAsset(updatedAsset); return toAsset(updatedAsset);
}), }),
); );
@ -106,20 +106,15 @@
} }
}; };
const hasGps = (asset: TimelineAsset) => { const hasGps = (asset: Asset) => {
return !!asset.latitude && !!asset.longitude; return !!asset.latitude && !!asset.longitude;
}; };
const handleThumbnailClick = ( const handleThumbnailClick = (
asset: TimelineAsset, asset: Asset,
timelineManager: TimelineManager, timelineManager: TimelineManager,
dayGroup: DayGroup, dayGroup: DayGroup,
onClick: ( onClick: (timelineManager: TimelineManager, assets: Asset[], groupTitle: string, asset: Asset) => void,
timelineManager: TimelineManager,
assets: TimelineAsset[],
groupTitle: string,
asset: TimelineAsset,
) => void,
) => { ) => {
if (hasGps(asset)) { if (hasGps(asset)) {
locationUpdated = true; locationUpdated = true;
@ -195,7 +190,7 @@
withStacked withStacked
onThumbnailClick={handleThumbnailClick} onThumbnailClick={handleThumbnailClick}
> >
{#snippet customLayout(asset: TimelineAsset)} {#snippet customLayout(asset: Asset)}
{#if hasGps(asset)} {#if hasGps(asset)}
<div class="absolute bottom-1 end-3 px-4 py-1 rounded-xl text-xs transition-colors bg-success text-black"> <div class="absolute bottom-1 end-3 px-4 py-1 rounded-xl text-xs transition-colors bg-success text-black">
{asset.city || $t('gps')} {asset.city || $t('gps')}

View file

@ -1,4 +1,4 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { Asset } from '$lib/managers/timeline-manager/types';
import { fromISODateTimeUTCToObject, fromTimelinePlainDateTime } from '$lib/utils/timeline-util'; import { fromISODateTimeUTCToObject, fromTimelinePlainDateTime } from '$lib/utils/timeline-util';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk'; import { AssetTypeEnum, AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
@ -30,7 +30,7 @@ export const assetFactory = Sync.makeFactory<AssetResponseDto>({
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
}); });
export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({ export const timelineAssetFactory = Sync.makeFactory<Asset>({
id: Sync.each(() => faker.string.uuid()), id: Sync.each(() => faker.string.uuid()),
ratio: Sync.each(() => faker.number.int()), ratio: Sync.each(() => faker.number.int()),
ownerId: Sync.each(() => faker.string.uuid()), ownerId: Sync.each(() => faker.string.uuid()),
@ -51,7 +51,7 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({
people: [faker.person.fullName()], people: [faker.person.fullName()],
}); });
export const toResponseDto = (...timelineAsset: TimelineAsset[]) => { export const toResponseDto = (...timelineAsset: Asset[]) => {
const bucketAssets: TimeBucketAssetResponseDto = { const bucketAssets: TimeBucketAssetResponseDto = {
city: [], city: [],
country: [], country: [],