diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs index ef17242c8e..2b89e5dc7d 100644 --- a/web/.eslintrc.cjs +++ b/web/.eslintrc.cjs @@ -13,6 +13,7 @@ module.exports = { sourceType: 'module', ecmaVersion: 2022, extraFileExtensions: ['.svelte'], + project: ['./tsconfig.json'], }, env: { browser: true, @@ -32,13 +33,6 @@ module.exports = { NodeJS: true, }, rules: { - 'unicorn/no-useless-undefined': 'off', - 'unicorn/prefer-spread': 'off', - 'unicorn/no-null': 'off', - 'unicorn/prevent-abbreviations': 'off', - 'unicorn/no-nested-ternary': 'off', - 'unicorn/consistent-function-scoping': 'off', - 'unicorn/prefer-top-level-await': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', { @@ -48,5 +42,17 @@ module.exports = { }, ], curly: 2, + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-spread': 'off', + 'unicorn/no-null': 'off', + 'unicorn/prevent-abbreviations': 'off', + 'unicorn/no-nested-ternary': 'off', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/prefer-top-level-await': 'off', + // TODO: set recommended-type-checked and remove these rules + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/require-await': 'error', }, }; diff --git a/web/package-lock.json b/web/package-lock.json index 78e5caf7c5..bfbd6b18f9 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -32,7 +32,7 @@ "@socket.io/component-emitter": "^3.1.0", "@sveltejs/adapter-static": "^3.0.1", "@sveltejs/enhanced-img": "^0.1.8", - "@sveltejs/kit": "^2.5.1", + "@sveltejs/kit": "^2.5.2", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/svelte": "^4.0.3", @@ -1859,9 +1859,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.1.tgz", - "integrity": "sha512-TKj08o3mJCoQNLTdRdGkHPePTCPUGTgkew65RDqjVU3MtPVxljsofXQYfXndHfq0P7KoPRO/0/reF6HesU0Djw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.2.tgz", + "integrity": "sha512-1Pm2lsBYURQsjnLyZa+jw75eVD4gYHxGRwPyFe4DAmB3FjTVR8vRNWGeuDLGFcKMh/B1ij6FTUrc9GrerogCng==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/web/package.json b/web/package.json index 2b53d06451..e91f2c68d7 100644 --- a/web/package.json +++ b/web/package.json @@ -27,7 +27,7 @@ "@socket.io/component-emitter": "^3.1.0", "@sveltejs/adapter-static": "^3.0.1", "@sveltejs/enhanced-img": "^0.1.8", - "@sveltejs/kit": "^2.5.1", + "@sveltejs/kit": "^2.5.2", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/svelte": "^4.0.3", diff --git a/web/src/hooks.client.ts b/web/src/hooks.client.ts index 802e9a7122..f30e3ee8ca 100644 --- a/web/src/hooks.client.ts +++ b/web/src/hooks.client.ts @@ -1,7 +1,6 @@ import { isHttpError } from '@immich/sdk'; import type { HandleClientError } from '@sveltejs/kit'; -const LOG_PREFIX = '[hooks.client.ts]'; const DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?'; const parseError = (error: unknown) => { @@ -23,6 +22,6 @@ const parseError = (error: unknown) => { export const handleError: HandleClientError = ({ error }) => { const result = parseError(error); - console.error(`${LOG_PREFIX}:handleError ${result.message}`); + console.error(`[hooks.client.ts]:handleError ${result.message}`); return result; }; diff --git a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte index 60756166f2..edf5c48300 100644 --- a/web/src/lib/components/admin-page/jobs/jobs-panel.svelte +++ b/web/src/lib/components/admin-page/jobs/jobs-panel.svelte @@ -48,11 +48,11 @@ await handleCommand(jobId, dto); }; - const onConfirm = () => { + const onConfirm = async () => { if (!confirmJob) { return; } - handleCommand(confirmJob, { command: JobCommand.Start, force: true }); + await handleCommand(confirmJob, { command: JobCommand.Start, force: true }); confirmJob = null; }; diff --git a/web/src/lib/components/admin-page/settings/admin-settings.svelte b/web/src/lib/components/admin-page/settings/admin-settings.svelte index 1ad962b0de..16b2afc7fe 100644 --- a/web/src/lib/components/admin-page/settings/admin-settings.svelte +++ b/web/src/lib/components/admin-page/settings/admin-settings.svelte @@ -54,7 +54,7 @@ }); }; - const resetToDefault = async (configKeys: Array) => { + const resetToDefault = (configKeys: Array) => { for (const key of configKeys) { config = { ...config, [key]: defaultConfig[key] }; } diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index e9983cc7ec..d4f60ab60a 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -21,6 +21,7 @@ import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js'; import UpdatePanel from '../shared-components/update-panel.svelte'; + import { handlePromiseError } from '$lib/utils'; export let sharedLink: SharedLinkResponseDto; export let user: UserResponseDto | undefined = undefined; @@ -35,7 +36,7 @@ dragAndDropFilesStore.subscribe((value) => { if (value.isDragging && value.files.length > 0) { - fileUploadHandler(value.files, album.id); + handlePromiseError(fileUploadHandler(value.files, album.id)); dragAndDropFilesStore.set({ isDragging: false, files: [] }); } }); @@ -67,7 +68,7 @@ const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); - onMount(async () => { + onMount(() => { document.addEventListener('keydown', onKeyboardPress); }); diff --git a/web/src/lib/components/asset-viewer/activity-viewer.svelte b/web/src/lib/components/asset-viewer/activity-viewer.svelte index dff2d7f319..4dd1b75e79 100644 --- a/web/src/lib/components/asset-viewer/activity-viewer.svelte +++ b/web/src/lib/components/asset-viewer/activity-viewer.svelte @@ -1,7 +1,7 @@ diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index 38f65e3df7..7f94857afc 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -7,7 +7,7 @@ import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { websocketEvents } from '$lib/stores/websocket'; - import { getAssetThumbnailUrl, getPeopleThumbnailUrl, isSharedLink } from '$lib/utils'; + import { getAssetThumbnailUrl, getPeopleThumbnailUrl, isSharedLink, handlePromiseError } from '$lib/utils'; import { delay, getAssetFilename } from '$lib/utils/asset-utils'; import { autoGrowHeight } from '$lib/utils/autogrow'; import { clickOutside } from '$lib/utils/click-outside'; @@ -78,7 +78,7 @@ originalDescription = description; }; - $: handleNewAsset(asset); + $: handlePromiseError(handleNewAsset(asset)); $: latlng = (() => { const lat = asset.exifInfo?.latitude; @@ -113,7 +113,7 @@ switch (event.key) { case 'Enter': { if (ctrl && event.target === textArea) { - handleFocusOut(); + await handleFocusOut(); } } } diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index e10d5573ca..79e8276153 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -4,7 +4,7 @@ import { boundingBoxesArray } from '$lib/stores/people.store'; import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store'; import { photoZoomState } from '$lib/stores/zoom-image.store'; - import { getKey } from '$lib/utils'; + import { getKey, handlePromiseError } from '$lib/utils'; import { isWebCompatibleImage } from '$lib/utils/asset-utils'; import { getBoundingBox } from '$lib/utils/people-utils'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; @@ -102,7 +102,7 @@ } }; - const doZoomImage = async () => { + const doZoomImage = () => { setZoomImageWheelState({ currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1, }); @@ -120,7 +120,7 @@ if (state.currentZoom > 1 && isWebCompatibleImage(asset) && !hasZoomed && !$alwaysLoadOriginalFile) { hasZoomed = true; - loadAssetData({ loadOriginal: true }); + handlePromiseError(loadAssetData({ loadOriginal: true })); } }); diff --git a/web/src/lib/components/asset-viewer/video-viewer.svelte b/web/src/lib/components/asset-viewer/video-viewer.svelte index 96713d6217..f5ff5f0fc2 100644 --- a/web/src/lib/components/asset-viewer/video-viewer.svelte +++ b/web/src/lib/components/asset-viewer/video-viewer.svelte @@ -20,7 +20,7 @@ video.muted = false; dispatch('onVideoStarted'); } catch (error) { - await handleError(error, 'Unable to play video'); + handleError(error, 'Unable to play video'); } finally { isVideoLoading = false; } diff --git a/web/src/lib/components/faces-page/assign-face-side-panel.svelte b/web/src/lib/components/faces-page/assign-face-side-panel.svelte index 6b8d8af174..ce7212f950 100644 --- a/web/src/lib/components/faces-page/assign-face-side-panel.svelte +++ b/web/src/lib/components/faces-page/assign-face-side-panel.svelte @@ -49,7 +49,7 @@ if (assetType === AssetTypeEnum.Image) { image = $photoViewer; } else if (assetType === AssetTypeEnum.Video) { - const data = await getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp); + const data = getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp); const img: HTMLImageElement = new Image(); img.src = data; diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte index 434bfb88ae..7b075405ca 100644 --- a/web/src/lib/components/faces-page/merge-face-selector.svelte +++ b/web/src/lib/components/faces-page/merge-face-selector.svelte @@ -43,10 +43,10 @@ dispatch('back'); }; - const handleSwapPeople = () => { + const handleSwapPeople = async () => { [person, selectedPeople[0]] = [selectedPeople[0], person]; $page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE); - goto(`${AppRoute.PEOPLE}/${person.id}?${$page.url.searchParams.toString()}`); + await goto(`${AppRoute.PEOPLE}/${person.id}?${$page.url.searchParams.toString()}`); }; const onSelect = (selected: PersonResponseDto) => { diff --git a/web/src/lib/components/faces-page/person-side-panel.svelte b/web/src/lib/components/faces-page/person-side-panel.svelte index 91a9587998..087301ea1e 100644 --- a/web/src/lib/components/faces-page/person-side-panel.svelte +++ b/web/src/lib/components/faces-page/person-side-panel.svelte @@ -3,7 +3,7 @@ import { timeBeforeShowLoadingSpinner } from '$lib/constants'; import { boundingBoxesArray } from '$lib/stores/people.store'; import { websocketEvents } from '$lib/stores/websocket'; - import { getPeopleThumbnailUrl } from '$lib/utils'; + import { getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { getPersonNameWithHiddenValue } from '$lib/utils/person'; import { @@ -85,7 +85,7 @@ }; onMount(() => { - loadPeople(); + handlePromiseError(loadPeople()); return websocketEvents.on('on_person_thumbnail', onPersonThumbnail); }); @@ -164,7 +164,7 @@ } }; - const handlePersonPicker = async (index: number) => { + const handlePersonPicker = (index: number) => { editedPersonIndex = index; showSeletecFaces = true; }; diff --git a/web/src/lib/components/faces-page/unmerge-face-selector.svelte b/web/src/lib/components/faces-page/unmerge-face-selector.svelte index 3c09b208d5..254594988d 100644 --- a/web/src/lib/components/faces-page/unmerge-face-selector.svelte +++ b/web/src/lib/components/faces-page/unmerge-face-selector.svelte @@ -132,9 +132,7 @@ title={'Assign selected assets to a new person'} size={'sm'} disabled={disableButtons || hasSelection} - on:click={() => { - handleCreate(); - }} + on:click={handleCreate} > {#if !showLoadingSpinnerCreate} @@ -147,9 +145,7 @@ size={'sm'} title={'Assign selected assets to an existing person'} disabled={disableButtons || !hasSelection} - on:click={() => { - handleReassign(); - }} + on:click={handleReassign} > {#if !showLoadingSpinnerReassign}
diff --git a/web/src/lib/components/forms/library-scan-settings-form.svelte b/web/src/lib/components/forms/library-scan-settings-form.svelte index f110a5a091..4d6c9c0dd5 100644 --- a/web/src/lib/components/forms/library-scan-settings-form.svelte +++ b/web/src/lib/components/forms/library-scan-settings-form.svelte @@ -37,7 +37,7 @@ dispatch('submit', { library, type: LibraryType.External }); }; - const handleAddExclusionPattern = async () => { + const handleAddExclusionPattern = () => { if (!addExclusionPattern) { return; } @@ -60,7 +60,7 @@ } }; - const handleEditExclusionPattern = async () => { + const handleEditExclusionPattern = () => { if (editExclusionPattern === null) { return; } @@ -79,7 +79,7 @@ } }; - const handleDeleteExclusionPattern = async () => { + const handleDeleteExclusionPattern = () => { if (editExclusionPattern === null) { return; } diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte index 9ea654a80c..202c6b60c3 100644 --- a/web/src/lib/components/forms/login-form.svelte +++ b/web/src/lib/components/forms/login-form.svelte @@ -47,7 +47,7 @@ return; } } catch (error) { - await handleError(error, 'Unable to connect!'); + handleError(error, 'Unable to connect!'); } oauthLoading = false; diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index c07672fd1c..30617f0aa9 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -8,7 +8,7 @@ import { AppRoute, QueryParameter } from '$lib/constants'; import type { Viewport } from '$lib/stores/assets.store'; import { memoryStore } from '$lib/stores/memory.store'; - import { getAssetThumbnailUrl } from '$lib/utils'; + import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; import { fromLocalDateTime } from '$lib/utils/timeline-util'; import { ThumbnailFormat, getMemoryLane } from '@immich/sdk'; import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js'; @@ -59,30 +59,30 @@ let paused = false; // Play or pause progress when the paused state changes. - $: paused ? pause() : play(); + $: paused ? handlePromiseError(pause()) : handlePromiseError(play()); // Progress should be paused when it's no longer possible to advance. $: paused ||= !canGoForward || galleryInView; // Advance to the next asset or memory when progress is complete. - $: $progress === 1 && toNext(); + $: $progress === 1 && handlePromiseError(toNext()); // Progress should be resumed when reset and not paused. - $: !$progress && !paused && play(); + $: !$progress && !paused && handlePromiseError(play()); // Progress should be reset when the current memory or asset changes. - $: memoryIndex, assetIndex, reset(); + $: memoryIndex, assetIndex, handlePromiseError(reset()); - const handleKeyDown = (e: KeyboardEvent) => { + const handleKeyDown = async (e: KeyboardEvent) => { if (e.key === 'ArrowRight' && canGoForward) { e.preventDefault(); - toNext(); + await toNext(); } else if (e.key === 'ArrowLeft' && canGoBack) { e.preventDefault(); - toPrevious(); + await toPrevious(); } else if (e.key === 'Escape') { e.preventDefault(); - goto(AppRoute.PHOTOS); + await goto(AppRoute.PHOTOS); } }; diff --git a/web/src/lib/components/photos-page/actions/add-to-album.svelte b/web/src/lib/components/photos-page/actions/add-to-album.svelte index c7101922f4..cb059947bb 100644 --- a/web/src/lib/components/photos-page/actions/add-to-album.svelte +++ b/web/src/lib/components/photos-page/actions/add-to-album.svelte @@ -27,18 +27,22 @@ showAlbumPicker = false; const assetIds = [...getAssets()].map((asset) => asset.id); - createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => { - const { id, albumName } = response; + createAlbum({ createAlbumDto: { albumName, assetIds } }) + .then(async (response) => { + const { id, albumName } = response; - notificationController.show({ - message: `Added ${assetIds.length} to ${albumName}`, - type: NotificationType.Info, + notificationController.show({ + message: `Added ${assetIds.length} to ${albumName}`, + type: NotificationType.Info, + }); + + clearSelect(); + + await goto(`${AppRoute.ALBUMS}/${id}`); + }) + .catch((error) => { + console.error(`[add-to-album.svelte]:handleAddToNewAlbum ${error}`, error); }); - - clearSelect(); - - goto(`${AppRoute.ALBUMS}/${id}`); - }); }; const handleAddToAlbum = async (album: AlbumResponseDto) => { diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index d8372525b6..0c40c87108 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -80,13 +80,17 @@ }); } - const assetClickHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => { + const assetClickHandler = async ( + asset: AssetResponseDto, + assetsInDateGroup: AssetResponseDto[], + groupTitle: string, + ) => { if (isSelectionMode || $isMultiSelectState) { assetSelectHandler(asset, assetsInDateGroup, groupTitle); return; } - assetViewingStore.setAssetId(asset.id); + await assetViewingStore.setAssetId(asset.id); }; const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets }); diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index bfb0edd7e1..a512adaad5 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -21,6 +21,7 @@ import ShowShortcuts from '../shared-components/show-shortcuts.svelte'; import AssetDateGroup from './asset-date-group.svelte'; import DeleteAssetDialog from './delete-asset-dialog.svelte'; + import { handlePromiseError } from '$lib/utils'; export let isSelectionMode = false; export let singleSelect = false; @@ -47,19 +48,19 @@ $: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0; $: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id); - const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>(); + const onKeydown = (event: KeyboardEvent) => handlePromiseError(handleKeyboardPress(event)); onMount(async () => { showSkeleton = false; - document.addEventListener('keydown', onKeyboardPress); + document.addEventListener('keydown', onKeydown); assetStore.connect(); await assetStore.init(viewport); }); onDestroy(() => { if (browser) { - document.removeEventListener('keydown', onKeyboardPress); + document.removeEventListener('keydown', onKeydown); } if ($showAssetViewer) { @@ -69,13 +70,13 @@ assetStore.disconnect(); }); - const trashOrDelete = (force: boolean = false) => { + const trashOrDelete = async (force: boolean = false) => { isShowDeleteConfirmation = false; - deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets); + await deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets); assetInteractionStore.clearMultiselect(); }; - const handleKeyboardPress = (event: KeyboardEvent) => { + const handleKeyboardPress = async (event: KeyboardEvent) => { if ($isSearchEnabled || shouldIgnoreShortcut(event)) { return; } @@ -98,7 +99,7 @@ } case '/': { event.preventDefault(); - goto(AppRoute.EXPLORE); + await goto(AppRoute.EXPLORE); return; } case 'Delete': { @@ -112,7 +113,7 @@ force = true; } - trashOrDelete(force); + await trashOrDelete(force); } return; } @@ -126,12 +127,12 @@ } }; - function intersectedHandler(event: CustomEvent) { + async function intersectedHandler(event: CustomEvent) { const element_ = event.detail.container as HTMLElement; const target = element_.firstChild as HTMLElement; if (target) { const bucketDate = target.id.split('_')[1]; - assetStore.loadBucket(bucketDate, event.detail.position); + await assetStore.loadBucket(bucketDate, event.detail.position); } } @@ -142,7 +143,7 @@ const handlePrevious = async () => { const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id); if (previousAsset) { - assetViewingStore.setAssetId(previousAsset); + await assetViewingStore.setAssetId(previousAsset); } return !!previousAsset; @@ -151,7 +152,7 @@ const handleNext = async () => { const nextAsset = await assetStore.getNextAssetId($viewingAsset.id); if (nextAsset) { - assetViewingStore.setAssetId(nextAsset); + await assetViewingStore.setAssetId(nextAsset); } return !!nextAsset; @@ -369,7 +370,7 @@ (isShowDeleteConfirmation = false)} - on:confirm={() => trashOrDelete(true)} + on:confirm={() => handlePromiseError(trashOrDelete(true))} /> {/if} diff --git a/web/src/lib/components/share-page/individual-shared-viewer.svelte b/web/src/lib/components/share-page/individual-shared-viewer.svelte index 0036331378..b587f1e13b 100644 --- a/web/src/lib/components/share-page/individual-shared-viewer.svelte +++ b/web/src/lib/components/share-page/individual-shared-viewer.svelte @@ -2,7 +2,7 @@ import { goto } from '$app/navigation'; import { AppRoute } from '$lib/constants'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; - import { getKey } from '$lib/utils'; + import { getKey, handlePromiseError } from '$lib/utils'; import { downloadArchive } from '$lib/utils/asset-utils'; import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader'; import { handleError } from '$lib/utils/handle-error'; @@ -29,7 +29,7 @@ dragAndDropFilesStore.subscribe((value) => { if (value.isDragging && value.files.length > 0) { - handleUploadAssets(value.files); + handlePromiseError(handleUploadAssets(value.files)); dragAndDropFilesStore.set({ isDragging: false, files: [] }); } }); @@ -59,7 +59,7 @@ type: NotificationType.Info, }); } catch (error) { - await handleError(error, 'Unable to add assets to shared link'); + handleError(error, 'Unable to add assets to shared link'); } }; diff --git a/web/src/lib/components/shared-components/map/map.svelte b/web/src/lib/components/shared-components/map/map.svelte index 682042604e..063d5971cd 100644 --- a/web/src/lib/components/shared-components/map/map.svelte +++ b/web/src/lib/components/shared-components/map/map.svelte @@ -2,7 +2,7 @@ import Icon from '$lib/components/elements/icon.svelte'; import { Theme } from '$lib/constants'; import { colorTheme, mapSettings } from '$lib/stores/preferences.store'; - import { getAssetThumbnailUrl } from '$lib/utils'; + import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; import { getMapStyle, MapTheme, type MapMarkerResponseDto } from '@immich/sdk'; import { mdiCog, mdiMapMarker } from '@mdi/js'; import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson'; @@ -152,9 +152,7 @@ applyToClusters asButton let:feature - on:click={(event) => { - handleClusterClick(event.detail.feature.properties.cluster_id, map); - }} + on:click={(event) => handlePromiseError(handleClusterClick(event.detail.feature.properties.cluster_id, map))} >
{ - progress.set(90); + onMount(async () => { + await progress.set(90); }); diff --git a/web/src/lib/components/shared-components/portal/portal.svelte b/web/src/lib/components/shared-components/portal/portal.svelte index c3dee4212c..924e5f0c6b 100644 --- a/web/src/lib/components/shared-components/portal/portal.svelte +++ b/web/src/lib/components/shared-components/portal/portal.svelte @@ -1,4 +1,5 @@ @@ -141,7 +142,7 @@ clearSearchTerm(searchTerm)} - on:selectSearchTerm={({ detail: searchTerm }) => onHistoryTermClick(searchTerm)} + on:selectSearchTerm={({ detail: searchTerm }) => handlePromiseError(onHistoryTermClick(searchTerm))} /> {/if} diff --git a/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte b/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte index adc824aec0..6f377faffb 100644 --- a/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte @@ -8,6 +8,7 @@ diff --git a/web/src/lib/components/shared-components/upload-asset-preview.svelte b/web/src/lib/components/shared-components/upload-asset-preview.svelte index 7d5a634fbf..4aa6f18ec5 100644 --- a/web/src/lib/components/shared-components/upload-asset-preview.svelte +++ b/web/src/lib/components/shared-components/upload-asset-preview.svelte @@ -13,9 +13,9 @@ export let uploadAsset: UploadAsset; - const handleRetry = (uploadAsset: UploadAsset) => { + const handleRetry = async (uploadAsset: UploadAsset) => { uploadAssetsStore.removeUploadAsset(uploadAsset.id); - fileUploadHandler([uploadAsset.file], uploadAsset.albumId); + await fileUploadHandler([uploadAsset.file], uploadAsset.albumId); }; diff --git a/web/src/lib/components/user-settings-page/library-list.svelte b/web/src/lib/components/user-settings-page/library-list.svelte index c11b41332f..440becde0b 100644 --- a/web/src/lib/components/user-settings-page/library-list.svelte +++ b/web/src/lib/components/user-settings-page/library-list.svelte @@ -56,8 +56,8 @@ let selectedLibraryIndex = 0; let selectedLibrary: LibraryResponseDto | null = null; - onMount(() => { - readLibraryList(); + onMount(async () => { + await readLibraryList(); }); const closeAll = () => { @@ -234,11 +234,11 @@ updateLibraryIndex = selectedLibraryIndex; }; - const onScanNewLibraryClicked = () => { + const onScanNewLibraryClicked = async () => { closeAll(); if (selectedLibrary) { - handleScan(selectedLibrary.id); + await handleScan(selectedLibrary.id); } }; @@ -248,38 +248,38 @@ updateLibraryIndex = selectedLibraryIndex; }; - const onScanAllLibraryFilesClicked = () => { + const onScanAllLibraryFilesClicked = async () => { closeAll(); if (selectedLibrary) { - handleScanChanges(selectedLibrary.id); + await handleScanChanges(selectedLibrary.id); } }; - const onForceScanAllLibraryFilesClicked = () => { + const onForceScanAllLibraryFilesClicked = async () => { closeAll(); if (selectedLibrary) { - handleForceScan(selectedLibrary.id); + await handleForceScan(selectedLibrary.id); } }; - const onRemoveOfflineFilesClicked = () => { + const onRemoveOfflineFilesClicked = async () => { closeAll(); if (selectedLibrary) { - handleRemoveOffline(selectedLibrary.id); + await handleRemoveOffline(selectedLibrary.id); } }; - const onDeleteLibraryClicked = () => { + const onDeleteLibraryClicked = async () => { closeAll(); if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) { - refreshStats(selectedLibraryIndex); + await refreshStats(selectedLibraryIndex); if (totalCount[selectedLibraryIndex] > 0) { deleteAssetCount = totalCount[selectedLibraryIndex]; confirmDeleteLibrary = selectedLibrary; } else { deletedLibrary = selectedLibrary; - handleDelete(); + await handleDelete(); } } }; @@ -348,27 +348,27 @@ {#if showContextMenu} - onMenuExit()}> - onRenameClicked()} text={`Rename`} /> + + {#if selectedLibrary && selectedLibrary.type === LibraryType.External} - onEditImportPathClicked()} text="Edit Import Paths" /> - onScanSettingClicked()} text="Scan Settings" /> + +
- onScanNewLibraryClicked()} text="Scan New Library Files" /> + onScanAllLibraryFilesClicked()} + on:click={onScanAllLibraryFilesClicked} text="Re-scan All Library Files" subtitle={'Only refreshes modified files'} /> onForceScanAllLibraryFilesClicked()} + on:click={onForceScanAllLibraryFilesClicked} text="Force Re-scan All Library Files" subtitle={'Refreshes every file'} />
- onRemoveOfflineFilesClicked()} text="Remove Offline Files" /> - onDeleteLibraryClicked()}> + +

Delete library

{/if} diff --git a/web/src/lib/components/user-settings-page/oauth-settings.svelte b/web/src/lib/components/user-settings-page/oauth-settings.svelte index 4ac27190bb..e17acd1678 100644 --- a/web/src/lib/components/user-settings-page/oauth-settings.svelte +++ b/web/src/lib/components/user-settings-page/oauth-settings.svelte @@ -28,7 +28,7 @@ } catch (error) { handleError(error, 'Unable to link OAuth account'); } finally { - goto('?open=oauth'); + await goto('?open=oauth'); } } diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte index f29cf42961..fbe5efa58a 100644 --- a/web/src/lib/components/user-settings-page/partner-settings.svelte +++ b/web/src/lib/components/user-settings-page/partner-settings.svelte @@ -31,8 +31,8 @@ let removePartnerDto: PartnerResponseDto | null = null; let partners: Array = []; - onMount(() => { - refreshPartners(); + onMount(async () => { + await refreshPartners(); }); const refreshPartners = async () => { diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 4f1f481a31..940877e36a 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -172,15 +172,17 @@ export class AssetStore { this.emit(false); let height = 0; + const loaders = []; for (const bucket of this.buckets) { if (height < viewport.height) { height += bucket.bucketHeight; - this.loadBucket(bucket.bucketDate, BucketPosition.Visible); + loaders.push(this.loadBucket(bucket.bucketDate, BucketPosition.Visible)); continue; } break; } + await Promise.all(loaders); } async loadBucket(bucketDate: string, position: BucketPosition): Promise { diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts index a025ef8cda..375cd3e75c 100644 --- a/web/src/lib/stores/websocket.ts +++ b/web/src/lib/stores/websocket.ts @@ -47,7 +47,7 @@ websocket .on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion)) .on('connect_error', (e) => console.log('Websocket Connect Error', e)); -export const openWebsocketConnection = async () => { +export const openWebsocketConnection = () => { try { if (!get(user)) { return; diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 644dd7f638..fa914bfcb2 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -194,3 +194,13 @@ export const findLocale = (code: string | undefined) => { name: language?.name, }; }; + +export const asyncTimeout = (ms: number) => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +}; + +export const handlePromiseError = (promise: Promise): void => { + promise.catch((error) => console.error(`[utils.ts]:handlePromiseError ${error}`, error)); +}; diff --git a/web/src/lib/utils/executor-queue.spec.ts b/web/src/lib/utils/executor-queue.spec.ts index 7533dc7686..b6ba77e9f3 100644 --- a/web/src/lib/utils/executor-queue.spec.ts +++ b/web/src/lib/utils/executor-queue.spec.ts @@ -28,10 +28,14 @@ describe('Executor Queue test', function () { }); // The first 3 should be finished within 200ms (concurrency 3) + // eslint-disable-next-line @typescript-eslint/no-floating-promises eq.addTask(() => timeoutPromiseBuilder(100, 'T1')); + // eslint-disable-next-line @typescript-eslint/no-floating-promises eq.addTask(() => timeoutPromiseBuilder(200, 'T2')); + // eslint-disable-next-line @typescript-eslint/no-floating-promises eq.addTask(() => timeoutPromiseBuilder(150, 'T3')); // The last task will be executed after 200ms and will finish at 400ms + // eslint-disable-next-line @typescript-eslint/no-floating-promises eq.addTask(() => timeoutPromiseBuilder(200, 'T4')); expect(finished).not.toBeCalled(); diff --git a/web/src/lib/utils/executor-queue.ts b/web/src/lib/utils/executor-queue.ts index 0744427cfc..7d3d3aa29f 100644 --- a/web/src/lib/utils/executor-queue.ts +++ b/web/src/lib/utils/executor-queue.ts @@ -1,3 +1,5 @@ +import { handlePromiseError } from '$lib/utils'; + interface Options { concurrency: number; } @@ -66,6 +68,6 @@ export class ExecutorQueue { return; } - runnable(); + handlePromiseError(runnable()); } } diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts index 929c1ee550..2f22803b6a 100644 --- a/web/src/lib/utils/file-uploader.ts +++ b/web/src/lib/utils/file-uploader.ts @@ -29,7 +29,7 @@ export const openFileUploadDialog = async (albumId?: string | undefined) => { fileSelector.type = 'file'; fileSelector.multiple = true; fileSelector.accept = extensions.join(','); - fileSelector.addEventListener('change', async (e: Event) => { + fileSelector.addEventListener('change', (e: Event) => { const target = e.target as HTMLInputElement; if (!target.files) { return; @@ -119,7 +119,7 @@ async function fileUploader(asset: File, albumId: string | undefined = undefined } }) .catch(async (error) => { - await handleError(error, 'Unable to upload file'); + handleError(error, 'Unable to upload file'); const reason = (await getServerErrorMessage(error)) || error; uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason }); return undefined; diff --git a/web/src/lib/utils/handle-error.ts b/web/src/lib/utils/handle-error.ts index 23727e4118..67aa3df5f4 100644 --- a/web/src/lib/utils/handle-error.ts +++ b/web/src/lib/utils/handle-error.ts @@ -22,20 +22,25 @@ export async function getServerErrorMessage(error: unknown) { } } -export async function handleError(error: unknown, message: string) { +export function handleError(error: unknown, message: string) { if ((error as Error)?.name === 'AbortError') { return; } console.error(`[handleError]: ${message}`, error, (error as Error)?.stack); - let serverMessage = await getServerErrorMessage(error); - if (serverMessage) { - serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`; - } + getServerErrorMessage(error) + .then((serverMessage) => { + if (serverMessage) { + serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`; + } - notificationController.show({ - message: serverMessage || message, - type: NotificationType.Error, - }); + notificationController.show({ + message: serverMessage || message, + type: NotificationType.Error, + }); + }) + .catch((error) => { + console.error(error); + }); } diff --git a/web/src/routes/(user)/albums/+page.svelte b/web/src/routes/(user)/albums/+page.svelte index 07000d9136..4f1cccbb0b 100644 --- a/web/src/routes/(user)/albums/+page.svelte +++ b/web/src/routes/(user)/albums/+page.svelte @@ -196,7 +196,7 @@ const handleCreateAlbum = async () => { const newAlbum = await createAlbum(); if (newAlbum) { - goto(`${AppRoute.ALBUMS}/${newAlbum.id}`); + await goto(`${AppRoute.ALBUMS}/${newAlbum.id}`); } }; @@ -204,8 +204,8 @@ return new Date(dateString).toLocaleDateString($locale, dateFormats.album); }; - onMount(() => { - removeAlbumsIfEmpty(); + onMount(async () => { + await removeAlbumsIfEmpty(); }); const removeAlbumsIfEmpty = async () => { diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index 7c9ca39acd..e511e67ed0 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -220,12 +220,11 @@ onMount(async () => { if (album.sharedUsers.length > 0) { - getFavorite(); - getNumberOfComments(); + await Promise.all([getFavorite(), getNumberOfComments()]); } }); - const handleKeypress = async (event: KeyboardEvent) => { + const handleKeypress = (event: KeyboardEvent) => { if (event.target !== textArea) { return; } @@ -242,12 +241,12 @@ const handleStartSlideshow = async () => { const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; if (asset) { - setAssetId(asset.id); + await setAssetId(asset.id); $slideshowState = SlideshowState.PlaySlideshow; } }; - const handleEscape = () => { + const handleEscape = async () => { if (viewMode === ViewMode.SELECT_USERS) { viewMode = ViewMode.VIEW; return; @@ -275,7 +274,7 @@ assetInteractionStore.clearMultiselect(); return; } - goto(backUrl); + await goto(backUrl); return; }; @@ -371,7 +370,7 @@ const handleRemoveUser = async (userId: string) => { if (userId == 'me' || userId === $user.id) { - goto(backUrl); + await goto(backUrl); return; } @@ -390,7 +389,7 @@ const handleRemoveAlbum = async () => { try { await deleteAlbum({ id: album.id }); - goto(backUrl); + await goto(backUrl); } catch (error) { handleError(error, 'Unable to delete album'); } finally { diff --git a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts index 687d74ea18..5c0ff2677d 100644 --- a/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/albums/[albumId]/photos/[assetId]/+page.ts @@ -2,7 +2,7 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load: PageLoad = async ({ params }) => { +export const load: PageLoad = ({ params }) => { const albumId = params.albumId; if (albumId) { diff --git a/web/src/routes/(user)/albums/albums.bloc.ts b/web/src/routes/(user)/albums/albums.bloc.ts index f608787fab..89a933e0f0 100644 --- a/web/src/routes/(user)/albums/albums.bloc.ts +++ b/web/src/routes/(user)/albums/albums.bloc.ts @@ -1,5 +1,6 @@ import type { OnShowContextMenuDetail } from '$lib/components/album-page/album-card'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; +import { asyncTimeout } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { createAlbum, deleteAlbum, getAllAlbums, type AlbumResponseDto } from '@immich/sdk'; import { derived, get, writable } from 'svelte/store'; @@ -20,9 +21,8 @@ export const useAlbums = (properties: AlbumsProperties) => { // Delete album that has no photos and is named '' for (const album of data) { if (album.albumName === '' && album.assetCount === 0) { - setTimeout(async () => { - await handleDeleteAlbum(album); - }, 500); + await asyncTimeout(500); + await handleDeleteAlbum(album); } } } catch { @@ -46,10 +46,7 @@ export const useAlbums = (properties: AlbumsProperties) => { albums.set(get(albums).filter(({ id }) => id !== albumToDelete.id)); } - async function showAlbumContextMenu( - contextMenuDetail: OnShowContextMenuDetail, - album: AlbumResponseDto, - ): Promise { + function showAlbumContextMenu(contextMenuDetail: OnShowContextMenuDetail, album: AlbumResponseDto): void { contextMenuTargetAlbum.set(album); contextMenuPosition.set({ diff --git a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts index 83c2975ca3..6a39c40daf 100644 --- a/web/src/routes/(user)/archive/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/archive/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load: PageLoad = async () => { +export const load: PageLoad = () => { redirect(302, AppRoute.ARCHIVE); }; diff --git a/web/src/routes/(user)/favorites/[assetId]/+page.ts b/web/src/routes/(user)/favorites/[assetId]/+page.ts index b5d469c2a6..5125ef1e45 100644 --- a/web/src/routes/(user)/favorites/[assetId]/+page.ts +++ b/web/src/routes/(user)/favorites/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load: PageLoad = async () => { +export const load: PageLoad = () => { redirect(302, AppRoute.FAVORITES); }; diff --git a/web/src/routes/(user)/map/+page.svelte b/web/src/routes/(user)/map/+page.svelte index a129b9fed9..257c0ad89d 100644 --- a/web/src/routes/(user)/map/+page.svelte +++ b/web/src/routes/(user)/map/+page.svelte @@ -15,6 +15,7 @@ import { DateTime, Duration } from 'luxon'; import { onDestroy, onMount } from 'svelte'; import type { PageData } from './$types'; + import { handlePromiseError } from '$lib/utils'; export let data: PageData; @@ -26,8 +27,8 @@ let viewingAssetCursor = 0; let showSettingsModal = false; - onMount(() => { - loadMapMarkers().then((data) => (mapMarkers = data)); + onMount(async () => { + mapMarkers = await loadMapMarkers(); }); onDestroy(() => { @@ -35,7 +36,7 @@ assetViewingStore.showAssetViewer(false); }); - $: $featureFlags.map || goto(AppRoute.PHOTOS); + $: $featureFlags.map || handlePromiseError(goto(AppRoute.PHOTOS)); const omit = (obj: MapSettings, key: string) => { return Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key)); }; @@ -85,21 +86,21 @@ } } - function onViewAssets(assetIds: string[]) { - assetViewingStore.setAssetId(assetIds[0]); + async function onViewAssets(assetIds: string[]) { + await assetViewingStore.setAssetId(assetIds[0]); viewingAssets = assetIds; viewingAssetCursor = 0; } - function navigateNext() { + async function navigateNext() { if (viewingAssetCursor < viewingAssets.length - 1) { - assetViewingStore.setAssetId(viewingAssets[++viewingAssetCursor]); + await assetViewingStore.setAssetId(viewingAssets[++viewingAssetCursor]); } } - function navigatePrevious() { + async function navigatePrevious() { if (viewingAssetCursor > 0) { - assetViewingStore.setAssetId(viewingAssets[--viewingAssetCursor]); + await assetViewingStore.setAssetId(viewingAssets[--viewingAssetCursor]); } } diff --git a/web/src/routes/(user)/memory/photos/[assetId]/+page.ts b/web/src/routes/(user)/memory/photos/[assetId]/+page.ts index 428a3caae9..f90b109917 100644 --- a/web/src/routes/(user)/memory/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/memory/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (() => { redirect(302, AppRoute.PHOTOS); }) satisfies PageLoad; diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 1372d254b9..6373b81fd5 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -81,12 +81,12 @@ const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); - onMount(() => { + onMount(async () => { document.addEventListener('keydown', onKeyboardPress); const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE); if (getSearchedPeople) { searchName = getSearchedPeople; - handleSearchPeople(true); + await handleSearchPeople(true); } }); @@ -108,10 +108,10 @@ } }; - const handleSearch = (force: boolean) => { + const handleSearch = async (force: boolean) => { $page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName); - goto($page.url); - handleSearchPeople(force); + await goto($page.url); + await handleSearchPeople(force); }; const handleCloseClick = () => { @@ -293,8 +293,8 @@ } }; - const handleMergePeople = (detail: PersonResponseDto) => { - goto( + const handleMergePeople = async (detail: PersonResponseDto) => { + await goto( `${AppRoute.PEOPLE}/${detail.id}?${QueryParameter.ACTION}=${ActionQueryParameterValue.MERGE}&${QueryParameter.PREVIOUS_ROUTE}=${AppRoute.PEOPLE}`, ); }; @@ -303,7 +303,7 @@ if (searchName === '') { if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) { $page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE); - goto($page.url); + await goto($page.url); } return; } @@ -331,7 +331,7 @@ return; } if (personName === '') { - changeName(); + await changeName(); return; } const data = await searchPerson({ name: personName, withHidden: true }); @@ -359,7 +359,7 @@ .slice(0, 3); return; } - changeName(); + await changeName(); }; const submitBirthDateChange = async (value: string) => { diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte index f572ff2cb7..dd4f2d7bd1 100644 --- a/web/src/routes/(user)/people/[personId]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/+page.svelte @@ -185,7 +185,7 @@ } }; - const handleEscape = () => { + const handleEscape = async () => { if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) { return; } @@ -193,7 +193,7 @@ assetInteractionStore.clearMultiselect(); return; } else { - goto(previousRoute); + await goto(previousRoute); return; } }; @@ -235,7 +235,7 @@ type: NotificationType.Info, }); - goto(previousRoute, { replaceState: true }); + await goto(previousRoute, { replaceState: true }); } catch (error) { handleError(error, 'Unable to hide person'); } @@ -244,7 +244,7 @@ const handleMerge = async (person: PersonResponseDto) => { const { assets } = await getPersonStatistics({ id: person.id }); numberOfAssets = assets; - handleGoBack(); + await handleGoBack(); data.person = person; @@ -292,7 +292,7 @@ refreshAssetGrid = !refreshAssetGrid; return; } - goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true }); + await goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true }); } catch (error) { handleError(error, 'Unable to save name'); } @@ -341,7 +341,7 @@ return; } if (name === '') { - changeName(); + await changeName(); return; } @@ -366,7 +366,7 @@ viewMode = ViewMode.SUGGEST_MERGE; return; } - changeName(); + await changeName(); }; const handleSetBirthDate = async (birthDate: string) => { @@ -392,11 +392,11 @@ } }; - const handleGoBack = () => { + const handleGoBack = async () => { viewMode = ViewMode.VIEW_ASSETS; if ($page.url.searchParams.has(QueryParameter.ACTION)) { $page.url.searchParams.delete(QueryParameter.ACTION); - goto($page.url); + await goto($page.url); } }; diff --git a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts index 5ac7adf5c9..bb2e587da4 100644 --- a/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/people/[personId]/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async ({ params }) => { +export const load = (({ params }) => { redirect(302, `${AppRoute.PEOPLE}/${params.personId}`); }) satisfies PageLoad; diff --git a/web/src/routes/(user)/photos/[assetId]/+page.ts b/web/src/routes/(user)/photos/[assetId]/+page.ts index 428a3caae9..f90b109917 100644 --- a/web/src/routes/(user)/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (() => { redirect(302, AppRoute.PHOTOS); }) satisfies PageLoad; diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte index bda74db788..28ad6bcbd7 100644 --- a/web/src/routes/(user)/search/+page.svelte +++ b/web/src/routes/(user)/search/+page.svelte @@ -35,6 +35,7 @@ import type { Viewport } from '$lib/stores/assets.store'; import { locale } from '$lib/stores/preferences.store'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; + import { handlePromiseError } from '$lib/utils'; import { parseUtcDate } from '$lib/utils/date-time'; const MAX_ASSET_COUNT = 5000; @@ -53,7 +54,7 @@ const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); - const handleKeyboardPress = (event: KeyboardEvent) => { + const handleKeyboardPress = async (event: KeyboardEvent) => { if (shouldIgnoreShortcut(event)) { return; } @@ -65,7 +66,7 @@ return; } if (!$preventRaceConditionSearchBar) { - goto(previousRoute); + await goto(previousRoute); } $preventRaceConditionSearchBar = false; return; @@ -108,13 +109,13 @@ return searchQuery ? JSON.parse(searchQuery) : {}; })(); - $: terms, onSearchQueryUpdate(); + $: terms, handlePromiseError(onSearchQueryUpdate()); async function onSearchQueryUpdate() { nextPage = 1; searchResultAssets = []; searchResultAlbums = []; - loadNextPage(); + await loadNextPage(); } export const loadNextPage = async () => { diff --git a/web/src/routes/(user)/search/photos/[assetId]/+page.ts b/web/src/routes/(user)/search/photos/[assetId]/+page.ts index 3c4bafa3ef..f1e5126931 100644 --- a/web/src/routes/(user)/search/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/search/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (() => { redirect(302, AppRoute.SEARCH); }) satisfies PageLoad; diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte index 602a3a2e01..b50cb5089b 100644 --- a/web/src/routes/(user)/sharing/+page.svelte +++ b/web/src/routes/(user)/sharing/+page.svelte @@ -19,7 +19,7 @@ const createSharedAlbum = async () => { try { const newAlbum = await createAlbum({ createAlbumDto: { albumName: '' } }); - goto(`${AppRoute.ALBUMS}/${newAlbum.id}`); + await goto(`${AppRoute.ALBUMS}/${newAlbum.id}`); } catch (error) { handleError(error, 'Unable to create album'); } diff --git a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte index 394e74faf8..3a6ff6ac0a 100644 --- a/web/src/routes/(user)/sharing/sharedlinks/+page.svelte +++ b/web/src/routes/(user)/sharing/sharedlinks/+page.svelte @@ -40,7 +40,7 @@ deleteLinkId = null; await refresh(); } catch (error) { - await handleError(error, 'Unable to delete shared link'); + handleError(error, 'Unable to delete shared link'); } }; diff --git a/web/src/routes/(user)/trash/+page.svelte b/web/src/routes/(user)/trash/+page.svelte index 3dc126d7b6..c2398563ec 100644 --- a/web/src/routes/(user)/trash/+page.svelte +++ b/web/src/routes/(user)/trash/+page.svelte @@ -24,10 +24,11 @@ import { emptyTrash, restoreTrash } from '@immich/sdk'; import { mdiDeleteOutline, mdiHistory } from '@mdi/js'; import type { PageData } from './$types'; + import { handlePromiseError } from '$lib/utils'; export let data: PageData; - $: $featureFlags.trash || goto(AppRoute.PHOTOS); + $featureFlags.trash || handlePromiseError(goto(AppRoute.PHOTOS)); const assetStore = new AssetStore({ isTrashed: true }); const assetInteractionStore = createAssetInteractionStore(); diff --git a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts b/web/src/routes/(user)/trash/photos/[assetId]/+page.ts index 0474207e32..eb3a453d24 100644 --- a/web/src/routes/(user)/trash/photos/[assetId]/+page.ts +++ b/web/src/routes/(user)/trash/photos/[assetId]/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (() => { redirect(302, AppRoute.TRASH); }) satisfies PageLoad; diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts index ab0a19f1c2..ffb36cf352 100644 --- a/web/src/routes/+layout.ts +++ b/web/src/routes/+layout.ts @@ -3,7 +3,7 @@ import type { LayoutLoad } from './$types'; export const ssr = false; export const csr = true; -export const load = (async () => { +export const load = (() => { return { meta: { title: 'Immich', diff --git a/web/src/routes/admin/+page.ts b/web/src/routes/admin/+page.ts index e4f090a069..0d53c4ef2b 100644 --- a/web/src/routes/admin/+page.ts +++ b/web/src/routes/admin/+page.ts @@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants'; import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async () => { +export const load = (() => { redirect(302, AppRoute.ADMIN_USER_MANAGEMENT); }) satisfies PageLoad; diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index 38ecaa62f5..2f6c98af13 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -4,6 +4,7 @@ import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { AppRoute } from '$lib/constants'; + import { asyncTimeout } from '$lib/utils'; import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk'; import { mdiCog } from '@mdi/js'; import { onDestroy, onMount } from 'svelte'; @@ -11,21 +12,19 @@ export let data: PageData; - let timer: ReturnType; - let jobs: AllJobStatusResponseDto; - const load = async () => { - jobs = await getAllJobsStatus(); - }; + let running = true; onMount(async () => { - await load(); - timer = setInterval(load, 5000); + while (running) { + jobs = await getAllJobsStatus(); + await asyncTimeout(5000); + } }); onDestroy(() => { - clearInterval(timer); + running = false; }); diff --git a/web/src/routes/admin/server-status/+page.svelte b/web/src/routes/admin/server-status/+page.svelte index bf2830f15b..54f62b3adb 100644 --- a/web/src/routes/admin/server-status/+page.svelte +++ b/web/src/routes/admin/server-status/+page.svelte @@ -4,19 +4,21 @@ import { getServerStatistics } from '@immich/sdk'; import { onDestroy, onMount } from 'svelte'; import type { PageData } from './$types'; + import { asyncTimeout } from '$lib/utils'; export let data: PageData; - let setIntervalHandler: ReturnType; + let running = true; onMount(async () => { - setIntervalHandler = setInterval(async () => { + while (running) { data.stats = await getServerStatistics(); - }, 5000); + await asyncTimeout(5000); + } }); onDestroy(() => { - clearInterval(setIntervalHandler); + running = false; }); diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte index 73237915b6..a3a93e0f35 100644 --- a/web/src/routes/admin/user-management/+page.svelte +++ b/web/src/routes/admin/user-management/+page.svelte @@ -51,7 +51,7 @@ shouldShowCreateUserForm = false; }; - const editUserHandler = async (user: UserResponseDto) => { + const editUserHandler = (user: UserResponseDto) => { selectedUser = user; shouldShowEditUserForm = true; }; @@ -67,7 +67,7 @@ shouldShowInfoPanel = true; }; - const deleteUserHandler = async (user: UserResponseDto) => { + const deleteUserHandler = (user: UserResponseDto) => { selectedUser = user; shouldShowDeleteConfirmDialog = true; }; @@ -82,7 +82,7 @@ shouldShowDeleteConfirmDialog = false; }; - const restoreUserHandler = async (user: UserResponseDto) => { + const restoreUserHandler = (user: UserResponseDto) => { selectedUser = user; shouldShowRestoreDialog = true; }; diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte index f56169ddb7..277b3c0040 100644 --- a/web/src/routes/auth/change-password/+page.svelte +++ b/web/src/routes/auth/change-password/+page.svelte @@ -3,10 +3,17 @@ import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte'; import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; import { AppRoute } from '$lib/constants'; - import { user } from '$lib/stores/user.store'; + import { resetSavedUser, user } from '$lib/stores/user.store'; + import { logout } from '@immich/sdk'; import type { PageData } from './$types'; export let data: PageData; + + const onSuccess = async () => { + await goto(AppRoute.AUTH_LOGIN); + resetSavedUser(); + await logout(); + }; @@ -18,5 +25,5 @@ enter the new password below.

- goto(AppRoute.AUTH_LOGIN)} /> +
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 199cd06e36..9c22439c56 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,21 +1,12 @@ {#if $featureFlags.loaded} diff --git a/web/src/routes/auth/onboarding/+page.svelte b/web/src/routes/auth/onboarding/+page.svelte index 4cf42d4d3d..09139a7f7e 100644 --- a/web/src/routes/auth/onboarding/+page.svelte +++ b/web/src/routes/auth/onboarding/+page.svelte @@ -29,17 +29,17 @@ const handleDoneClicked = async () => { if (index >= onboardingSteps.length - 1) { await setAdminOnboarding(); - goto(AppRoute.PHOTOS); + await goto(AppRoute.PHOTOS); } else { index++; - goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`); + await goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`); } }; - const handlePrevious = () => { + const handlePrevious = async () => { if (index >= 1) { index--; - goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`); + await goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`); } }; diff --git a/web/svelte.config.js b/web/svelte.config.js index 0081e8e76b..3cb982c6b8 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -4,12 +4,6 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), - onwarn: (warning, handler) => { - if (warning.code.includes('a11y')) { - return; - } - handler(warning); - }, kit: { adapter: adapter({ // default options are shown. On some platforms