diff --git a/.vscode/settings.json b/.vscode/settings.json index 49692809bc..8ca2e47e61 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,11 @@ "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.tabSize": 2, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.removeUnusedImports": "explicit", + "source.organizeImports": "explicit" + } }, "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode", @@ -17,13 +21,14 @@ }, "[svelte]": { "editor.defaultFormatter": "svelte.svelte-vscode", - "editor.tabSize": 2 + "editor.tabSize": 2, + "editor.codeActionsOnSave": { + "source.removeUnusedImports": "explicit", + "source.organizeImports": "explicit" + } }, "svelte.enable-ts-plugin": true, - "eslint.validate": [ - "javascript", - "svelte" - ], + "eslint.validate": ["javascript", "svelte"], "typescript.preferences.importModuleSpecifier": "non-relative", "[dart]": { "editor.formatOnSave": true, @@ -34,12 +39,10 @@ "editor.wordBasedSuggestions": "off", "editor.defaultFormatter": "Dart-Code.dart-code" }, - "cSpell.words": [ - "immich" - ], + "cSpell.words": ["immich"], "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "*.ts": "${capture}.spec.ts,${capture}.mock.ts", "*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart" } -} \ No newline at end of file +} diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index ea9e7e3dd1..b5e70958d7 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -1,25 +1,25 @@ diff --git a/web/src/lib/components/asset-viewer/actions/archive-action.svelte b/web/src/lib/components/asset-viewer/actions/archive-action.svelte index ed19dff864..362a0a693a 100644 --- a/web/src/lib/components/asset-viewer/actions/archive-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/archive-action.svelte @@ -4,6 +4,7 @@ import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import { AssetAction } from '$lib/constants'; import { toggleArchive } from '$lib/utils/asset-utils'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import type { AssetResponseDto } from '@immich/sdk'; import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -18,11 +19,11 @@ const onArchive = async () => { if (!asset.isArchived) { - preAction({ type: AssetAction.ARCHIVE, asset }); + preAction({ type: AssetAction.ARCHIVE, asset: toTimelineAsset(asset) }); } const updatedAsset = await toggleArchive(asset); if (updatedAsset) { - onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset }); + onAction({ type: asset.isArchived ? AssetAction.ARCHIVE : AssetAction.UNARCHIVE, asset: toTimelineAsset(asset) }); } }; diff --git a/web/src/lib/components/asset-viewer/actions/delete-action.svelte b/web/src/lib/components/asset-viewer/actions/delete-action.svelte index 24ba2c845d..90322c00f0 100644 --- a/web/src/lib/components/asset-viewer/actions/delete-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/delete-action.svelte @@ -11,6 +11,7 @@ import { showDeleteModal } from '$lib/stores/preferences.store'; import { featureFlags } from '$lib/stores/server-config.store'; import { handleError } from '$lib/utils/handle-error'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import { deleteAssets, type AssetResponseDto } from '@immich/sdk'; import { mdiDeleteForeverOutline, mdiDeleteOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -42,9 +43,9 @@ const trashAsset = async () => { try { - preAction({ type: AssetAction.TRASH, asset }); + preAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) }); await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } }); - onAction({ type: AssetAction.TRASH, asset }); + onAction({ type: AssetAction.TRASH, asset: toTimelineAsset(asset) }); notificationController.show({ message: $t('moved_to_trash'), @@ -58,7 +59,7 @@ const deleteAsset = async () => { try { await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } }); - onAction({ type: AssetAction.DELETE, asset }); + onAction({ type: AssetAction.DELETE, asset: toTimelineAsset(asset) }); notificationController.show({ message: $t('permanently_deleted_asset'), diff --git a/web/src/lib/components/asset-viewer/actions/favorite-action.svelte b/web/src/lib/components/asset-viewer/actions/favorite-action.svelte index 0cc3188d51..bb1a9343d9 100644 --- a/web/src/lib/components/asset-viewer/actions/favorite-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/favorite-action.svelte @@ -7,6 +7,7 @@ } from '$lib/components/shared-components/notification/notification'; import { AssetAction } from '$lib/constants'; import { handleError } from '$lib/utils/handle-error'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import { updateAsset, type AssetResponseDto } from '@immich/sdk'; import { mdiHeart, mdiHeartOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -30,7 +31,10 @@ asset = { ...asset, isFavorite: data.isFavorite }; - onAction({ type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE, asset }); + onAction({ + type: asset.isFavorite ? AssetAction.FAVORITE : AssetAction.UNFAVORITE, + asset: toTimelineAsset(asset), + }); notificationController.show({ type: NotificationType.Info, diff --git a/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte index 8705476d8d..d5bc1db9bf 100644 --- a/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte +++ b/web/src/lib/components/asset-viewer/actions/keep-this-delete-others.svelte @@ -1,12 +1,13 @@ diff --git a/web/src/lib/components/asset-viewer/actions/restore-action.svelte b/web/src/lib/components/asset-viewer/actions/restore-action.svelte index abcae5c4c9..c790dab853 100644 --- a/web/src/lib/components/asset-viewer/actions/restore-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/restore-action.svelte @@ -6,6 +6,7 @@ } from '$lib/components/shared-components/notification/notification'; import { AssetAction } from '$lib/constants'; import { handleError } from '$lib/utils/handle-error'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import { restoreAssets, type AssetResponseDto } from '@immich/sdk'; import { mdiHistory } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -23,7 +24,7 @@ await restoreAssets({ bulkIdsDto: { ids: [asset.id] } }); asset.isTrashed = false; - onAction({ type: AssetAction.RESTORE, asset }); + onAction({ type: AssetAction.RESTORE, asset: toTimelineAsset(asset) }); notificationController.show({ type: NotificationType.Info, diff --git a/web/src/lib/components/asset-viewer/actions/unstack-action.svelte b/web/src/lib/components/asset-viewer/actions/unstack-action.svelte index f2a50cce13..f389249183 100644 --- a/web/src/lib/components/asset-viewer/actions/unstack-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/unstack-action.svelte @@ -2,6 +2,7 @@ import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import { AssetAction } from '$lib/constants'; import { deleteStack } from '$lib/utils/asset-utils'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import type { StackResponseDto } from '@immich/sdk'; import { mdiImageMinusOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -17,7 +18,7 @@ const handleUnstack = async () => { const unstackedAssets = await deleteStack([stack.id]); if (unstackedAssets) { - onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets }); + onAction({ type: AssetAction.UNSTACK, assets: unstackedAssets.map((a) => toTimelineAsset(a)) }); } }; diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts index a25ea6bf90..d075ef4dbb 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.spec.ts @@ -13,8 +13,9 @@ describe('AssetViewerNavBar component', () => { showDownloadButton: false, showMotionPlayButton: false, showShareButton: false, + preAction: () => {}, onZoomImage: () => {}, - onCopyImage: () => {}, + onCopyImage: async () => {}, onAction: () => {}, onRunJob: () => {}, onPlaySlideshow: () => {}, diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 91461d574d..ac512b6766 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -15,6 +15,7 @@ import { getAssetJobMessage, getSharedLink, handlePromiseError, isSharedLink } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { SlideshowHistory } from '$lib/utils/slideshow-history'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import { AssetJobName, AssetTypeEnum, @@ -52,7 +53,7 @@ interface Props { asset: AssetResponseDto; - preloadAssets?: AssetResponseDto[]; + preloadAssets?: { id: string }[]; showNavigation?: boolean; withStacked?: boolean; isShared?: boolean; @@ -62,7 +63,7 @@ onAction?: OnAction | undefined; reactions?: ActivityResponseDto[]; showCloseButton?: boolean; - onClose: (dto: { asset: AssetResponseDto }) => void; + onClose: (asset: AssetResponseDto) => void; onNext: () => Promise; onPrevious: () => Promise; onRandom: () => Promise; @@ -267,7 +268,7 @@ }; const closeViewer = () => { - onClose({ asset }); + onClose(asset); }; const closeEditor = () => { @@ -605,8 +606,8 @@ imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }} brokenAssetClass="text-xs" dimmed={stackedAsset.id !== asset.id} - asset={stackedAsset} - onClick={(stackedAsset) => { + asset={toTimelineAsset(stackedAsset)} + onClick={() => { asset = stackedAsset; }} onMouseEvent={({ isMouseOver }) => handleStackedAssetMouseEvent(isMouseOver, stackedAsset)} diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index d3a9da3633..a67de24a74 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -10,7 +10,7 @@ import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils'; import { getBoundingBox } from '$lib/utils/people-utils'; import { getAltText } from '$lib/utils/thumbnail-util'; - import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; + import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; import { onDestroy, onMount } from 'svelte'; import { t } from 'svelte-i18n'; import { type SwipeCustomEvent, swipe } from 'svelte-gestures'; @@ -24,7 +24,7 @@ interface Props { asset: AssetResponseDto; - preloadAssets?: AssetResponseDto[] | undefined; + preloadAssets?: { id: string }[] | undefined; element?: HTMLDivElement | undefined; haveFadeTransition?: boolean; sharedLink?: SharedLinkResponseDto | undefined; @@ -68,12 +68,10 @@ $boundingBoxesArray = []; }); - const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: AssetResponseDto[]) => { + const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: { id: string }[]) => { for (const preloadAsset of preloadAssets || []) { - if (preloadAsset.type === AssetTypeEnum.Image) { - let img = new Image(); - img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash); - } + let img = new Image(); + img.src = getAssetUrl(preloadAsset.id, targetSize, null); } }; diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index 93a4e3c6cc..802c04cf16 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -4,8 +4,8 @@ import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store'; import { getAssetPlaybackUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils'; import { timeToSeconds } from '$lib/utils/date-time'; - import { getAltText } from '$lib/utils/thumbnail-util'; - import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; + // import { getAltText } from '$lib/utils/thumbnail-util'; + import { AssetMediaSize } from '@immich/sdk'; import { mdiArchiveArrowDownOutline, mdiCameraBurst, @@ -17,22 +17,23 @@ } from '@mdi/js'; import { thumbhash } from '$lib/actions/thumbhash'; + import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte'; + import { getFocusable } from '$lib/utils/focus-util'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; import { TUNABLES } from '$lib/utils/tunables'; + import { onMount } from 'svelte'; import type { ClassValue } from 'svelte/elements'; import { fade } from 'svelte/transition'; import ImageThumbnail from './image-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte'; - import { onMount } from 'svelte'; - import { getFocusable } from '$lib/utils/focus-util'; interface Props { - asset: AssetResponseDto; + asset: TimelineAsset; groupIndex?: number; - thumbnailSize?: number | undefined; - thumbnailWidth?: number | undefined; - thumbnailHeight?: number | undefined; + thumbnailSize?: number; + thumbnailWidth?: number; + thumbnailHeight?: number; selected?: boolean; focussed?: boolean; selectionCandidate?: boolean; @@ -44,10 +45,10 @@ imageClass?: ClassValue; brokenAssetClass?: ClassValue; dimmed?: boolean; - onClick?: ((asset: AssetResponseDto) => void) | undefined; - onSelect?: ((asset: AssetResponseDto) => void) | undefined; - onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined; - handleFocus?: (() => void) | undefined; + onClick?: (asset: TimelineAsset) => void; + onSelect?: (asset: TimelineAsset) => void; + onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void; + handleFocus?: () => void; } let { @@ -331,7 +332,7 @@ {/if} - {#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR} + {#if asset.isImage && asset.projectionType === ProjectionType.EQUIRECTANGULAR}
@@ -344,7 +345,7 @@
@@ -354,27 +355,28 @@
{/if}
+ ((loaded = true), (thumbError = errored))} /> - {#if asset.type === AssetTypeEnum.Video} + {#if asset.isVideo}
- {:else if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId} + {:else if asset.isImage && asset.livePhotoVideoId}
(); let progressBarController: Tween | undefined = $state(undefined); let videoPlayer: HTMLVideoElement | undefined = $state(); const asHref = (asset: AssetResponseDto) => `?${QueryParameter.ID}=${asset.id}`; diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte index ddc5f3cae4..4fc15f1d84 100644 --- a/web/src/lib/components/photos-page/actions/archive-action.svelte +++ b/web/src/lib/components/photos-page/actions/archive-action.svelte @@ -1,11 +1,11 @@ diff --git a/web/src/lib/components/photos-page/actions/select-all-assets.svelte b/web/src/lib/components/photos-page/actions/select-all-assets.svelte index cc3f75ab56..3c6bbcbb3d 100644 --- a/web/src/lib/components/photos-page/actions/select-all-assets.svelte +++ b/web/src/lib/components/photos-page/actions/select-all-assets.svelte @@ -1,14 +1,14 @@