1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

chore(web): cleanup promise handling (#7382)

* no-misused-promises

* no-floating-promises

* format

* revert for now

* remove load function

* require-await

* revert a few no-floating-promises changes that would cause no-misused-promises failures

* format

* fix a few more

* fix most remaining errors

* executor-queue

* executor-queue.spec

* remove duplicate comments by grouping rules

* upgrade sveltekit and enforce rules

* oops. move await

* try this

* just ignore for now since it's only a test

* run in parallel

* Update web/src/routes/admin/jobs-status/+page.svelte

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* remove Promise.resolve call

* rename function

* remove unnecessary warning silencing

* make handleError sync

* fix new errors from recently merged PR to main

* extract method

* use handlePromiseError

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Ben McCann 2024-02-27 08:37:37 -08:00 committed by GitHub
parent 57f25855d3
commit 907a95a746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 312 additions and 282 deletions

View file

@ -13,6 +13,7 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2022, ecmaVersion: 2022,
extraFileExtensions: ['.svelte'], extraFileExtensions: ['.svelte'],
project: ['./tsconfig.json'],
}, },
env: { env: {
browser: true, browser: true,
@ -32,13 +33,6 @@ module.exports = {
NodeJS: true, NodeJS: true,
}, },
rules: { 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': [ '@typescript-eslint/no-unused-vars': [
'warn', 'warn',
{ {
@ -48,5 +42,17 @@ module.exports = {
}, },
], ],
curly: 2, 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',
}, },
}; };

8
web/package-lock.json generated
View file

@ -32,7 +32,7 @@
"@socket.io/component-emitter": "^3.1.0", "@socket.io/component-emitter": "^3.1.0",
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/enhanced-img": "^0.1.8", "@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.5.1", "@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@sveltejs/vite-plugin-svelte": "^3.0.2",
"@testing-library/jest-dom": "^6.1.5", "@testing-library/jest-dom": "^6.1.5",
"@testing-library/svelte": "^4.0.3", "@testing-library/svelte": "^4.0.3",
@ -1859,9 +1859,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.1.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.2.tgz",
"integrity": "sha512-TKj08o3mJCoQNLTdRdGkHPePTCPUGTgkew65RDqjVU3MtPVxljsofXQYfXndHfq0P7KoPRO/0/reF6HesU0Djw==", "integrity": "sha512-1Pm2lsBYURQsjnLyZa+jw75eVD4gYHxGRwPyFe4DAmB3FjTVR8vRNWGeuDLGFcKMh/B1ij6FTUrc9GrerogCng==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {

View file

@ -27,7 +27,7 @@
"@socket.io/component-emitter": "^3.1.0", "@socket.io/component-emitter": "^3.1.0",
"@sveltejs/adapter-static": "^3.0.1", "@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/enhanced-img": "^0.1.8", "@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.5.1", "@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2", "@sveltejs/vite-plugin-svelte": "^3.0.2",
"@testing-library/jest-dom": "^6.1.5", "@testing-library/jest-dom": "^6.1.5",
"@testing-library/svelte": "^4.0.3", "@testing-library/svelte": "^4.0.3",

View file

@ -1,7 +1,6 @@
import { isHttpError } from '@immich/sdk'; import { isHttpError } from '@immich/sdk';
import type { HandleClientError } from '@sveltejs/kit'; 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 DEFAULT_MESSAGE = 'Hmm, not sure about that. Check the logs or open a ticket?';
const parseError = (error: unknown) => { const parseError = (error: unknown) => {
@ -23,6 +22,6 @@ const parseError = (error: unknown) => {
export const handleError: HandleClientError = ({ error }) => { export const handleError: HandleClientError = ({ error }) => {
const result = parseError(error); const result = parseError(error);
console.error(`${LOG_PREFIX}:handleError ${result.message}`); console.error(`[hooks.client.ts]:handleError ${result.message}`);
return result; return result;
}; };

View file

@ -48,11 +48,11 @@
await handleCommand(jobId, dto); await handleCommand(jobId, dto);
}; };
const onConfirm = () => { const onConfirm = async () => {
if (!confirmJob) { if (!confirmJob) {
return; return;
} }
handleCommand(confirmJob, { command: JobCommand.Start, force: true }); await handleCommand(confirmJob, { command: JobCommand.Start, force: true });
confirmJob = null; confirmJob = null;
}; };

View file

@ -54,7 +54,7 @@
}); });
}; };
const resetToDefault = async (configKeys: Array<keyof SystemConfigDto>) => { const resetToDefault = (configKeys: Array<keyof SystemConfigDto>) => {
for (const key of configKeys) { for (const key of configKeys) {
config = { ...config, [key]: defaultConfig[key] }; config = { ...config, [key]: defaultConfig[key] };
} }

View file

@ -21,6 +21,7 @@
import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js'; import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
import UpdatePanel from '../shared-components/update-panel.svelte'; import UpdatePanel from '../shared-components/update-panel.svelte';
import { handlePromiseError } from '$lib/utils';
export let sharedLink: SharedLinkResponseDto; export let sharedLink: SharedLinkResponseDto;
export let user: UserResponseDto | undefined = undefined; export let user: UserResponseDto | undefined = undefined;
@ -35,7 +36,7 @@
dragAndDropFilesStore.subscribe((value) => { dragAndDropFilesStore.subscribe((value) => {
if (value.isDragging && value.files.length > 0) { if (value.isDragging && value.files.length > 0) {
fileUploadHandler(value.files, album.id); handlePromiseError(fileUploadHandler(value.files, album.id));
dragAndDropFilesStore.set({ isDragging: false, files: [] }); dragAndDropFilesStore.set({ isDragging: false, files: [] });
} }
}); });
@ -67,7 +68,7 @@
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
onMount(async () => { onMount(() => {
document.addEventListener('keydown', onKeyboardPress); document.addEventListener('keydown', onKeyboardPress);
}); });

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { timeBeforeShowLoadingSpinner } from '$lib/constants'; import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { getAssetType } from '$lib/utils/asset-utils'; import { getAssetType } from '$lib/utils/asset-utils';
import { autoGrowHeight } from '$lib/utils/autogrow'; import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
@ -79,7 +79,7 @@
$: { $: {
if (assetId && previousAssetId != assetId) { if (assetId && previousAssetId != assetId) {
getReactions(); handlePromiseError(getReactions());
previousAssetId = assetId; previousAssetId = assetId;
} }
} }
@ -95,10 +95,10 @@
} }
}; };
const handleEnter = (event: KeyboardEvent) => { const handleEnter = async (event: KeyboardEvent) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
handleSendComment(); await handleSendComment();
return; return;
} }
}; };

View file

@ -10,7 +10,7 @@
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { stackAssetsStore } from '$lib/stores/stacked-asset.store'; import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { getAssetJobMessage, isSharedLink } from '$lib/utils'; import { getAssetJobMessage, isSharedLink, handlePromiseError } from '$lib/utils';
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils'; import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
@ -174,8 +174,8 @@
$: { $: {
if (isShared && asset.id) { if (isShared && asset.id) {
getFavorite(); handlePromiseError(getFavorite());
getNumberOfComments(); handlePromiseError(getNumberOfComments());
} }
} }
@ -184,9 +184,9 @@
if (value === SlideshowState.PlaySlideshow) { if (value === SlideshowState.PlaySlideshow) {
slideshowHistory.reset(); slideshowHistory.reset();
slideshowHistory.queue(asset.id); slideshowHistory.queue(asset.id);
handlePlaySlideshow(); handlePromiseError(handlePlaySlideshow());
} else if (value === SlideshowState.StopSlideshow) { } else if (value === SlideshowState.StopSlideshow) {
handleStopSlideshow(); handlePromiseError(handleStopSlideshow());
} }
}); });
@ -226,7 +226,7 @@
} }
}); });
$: asset.id && !sharedLink && handleGetAllAlbums(); // Update the album information when the asset ID changes $: asset.id && !sharedLink && handlePromiseError(handleGetAllAlbums()); // Update the album information when the asset ID changes
const handleGetAllAlbums = async () => { const handleGetAllAlbums = async () => {
if (isSharedLink()) { if (isSharedLink()) {
@ -247,7 +247,7 @@
isShowActivity = !isShowActivity; isShowActivity = !isShowActivity;
}; };
const handleKeypress = (event: KeyboardEvent) => { const handleKeypress = async (event: KeyboardEvent) => {
if (shouldIgnoreShortcut(event)) { if (shouldIgnoreShortcut(event)) {
return; return;
} }
@ -264,7 +264,7 @@
case 'a': case 'a':
case 'A': { case 'A': {
if (shiftKey) { if (shiftKey) {
toggleArchive(); await toggleArchive();
} }
return; return;
} }
@ -273,18 +273,18 @@
return; return;
} }
case 'ArrowRight': { case 'ArrowRight': {
navigateAssetForward(); await navigateAssetForward();
return; return;
} }
case 'd': case 'd':
case 'D': { case 'D': {
if (shiftKey) { if (shiftKey) {
downloadFile(asset); await downloadFile(asset);
} }
return; return;
} }
case 'Delete': { case 'Delete': {
trashOrDelete(shiftKey); await trashOrDelete(shiftKey);
return; return;
} }
case 'Escape': { case 'Escape': {
@ -296,7 +296,7 @@
return; return;
} }
case 'f': { case 'f': {
toggleFavorite(); await toggleFavorite();
return; return;
} }
case 'i': { case 'i': {
@ -326,7 +326,7 @@
slideshowHistory.queue(asset.id); slideshowHistory.queue(asset.id);
setAssetId(asset.id); await setAssetId(asset.id);
$restartSlideshowProgress = true; $restartSlideshowProgress = true;
}; };
@ -369,17 +369,17 @@
$isShowDetail = !$isShowDetail; $isShowDetail = !$isShowDetail;
}; };
const trashOrDelete = (force: boolean = false) => { const trashOrDelete = async (force: boolean = false) => {
if (force || !isTrashEnabled) { if (force || !isTrashEnabled) {
if ($showDeleteModal) { if ($showDeleteModal) {
isShowDeleteConfirmation = true; isShowDeleteConfirmation = true;
return; return;
} }
deleteAsset(); await deleteAsset();
return; return;
} }
trashAsset(); await trashAsset();
return; return;
}; };
@ -432,7 +432,7 @@
message: asset.isFavorite ? `Added to favorites` : `Removed from favorites`, message: asset.isFavorite ? `Added to favorites` : `Removed from favorites`,
}); });
} catch (error) { } catch (error) {
await handleError(error, `Unable to ${asset.isFavorite ? `add asset to` : `remove asset from`} favorites`); handleError(error, `Unable to ${asset.isFavorite ? `add asset to` : `remove asset from`} favorites`);
} }
}; };
@ -472,7 +472,7 @@
message: asset.isArchived ? `Added to archive` : `Removed from archive`, message: asset.isArchived ? `Added to archive` : `Removed from archive`,
}); });
} catch (error) { } catch (error) {
await handleError(error, `Unable to ${asset.isArchived ? `add asset to` : `remove asset from`} archive`); handleError(error, `Unable to ${asset.isArchived ? `add asset to` : `remove asset from`} archive`);
} }
}; };
@ -481,7 +481,7 @@
await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } }); await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) }); notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) });
} catch (error) { } catch (error) {
await handleError(error, `Unable to submit job`); handleError(error, `Unable to submit job`);
} }
}; };
@ -492,7 +492,7 @@
let assetViewerHtmlElement: HTMLElement; let assetViewerHtmlElement: HTMLElement;
const slideshowHistory = new SlideshowHistory((assetId: string) => { const slideshowHistory = new SlideshowHistory((assetId: string) => {
setAssetId(assetId); handlePromiseError(setAssetId(assetId));
$restartSlideshowProgress = true; $restartSlideshowProgress = true;
}); });
@ -550,7 +550,7 @@
dispatch('close'); dispatch('close');
notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 }); notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 });
} catch (error) { } catch (error) {
await handleError(error, `Unable to unstack`); handleError(error, `Unable to unstack`);
} }
}; };
</script> </script>

View file

@ -7,7 +7,7 @@
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { websocketEvents } from '$lib/stores/websocket'; 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 { delay, getAssetFilename } from '$lib/utils/asset-utils';
import { autoGrowHeight } from '$lib/utils/autogrow'; import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
@ -78,7 +78,7 @@
originalDescription = description; originalDescription = description;
}; };
$: handleNewAsset(asset); $: handlePromiseError(handleNewAsset(asset));
$: latlng = (() => { $: latlng = (() => {
const lat = asset.exifInfo?.latitude; const lat = asset.exifInfo?.latitude;
@ -113,7 +113,7 @@
switch (event.key) { switch (event.key) {
case 'Enter': { case 'Enter': {
if (ctrl && event.target === textArea) { if (ctrl && event.target === textArea) {
handleFocusOut(); await handleFocusOut();
} }
} }
} }

View file

@ -4,7 +4,7 @@
import { boundingBoxesArray } from '$lib/stores/people.store'; import { boundingBoxesArray } from '$lib/stores/people.store';
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store'; import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
import { photoZoomState } from '$lib/stores/zoom-image.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 { isWebCompatibleImage } from '$lib/utils/asset-utils';
import { getBoundingBox } from '$lib/utils/people-utils'; import { getBoundingBox } from '$lib/utils/people-utils';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
@ -102,7 +102,7 @@
} }
}; };
const doZoomImage = async () => { const doZoomImage = () => {
setZoomImageWheelState({ setZoomImageWheelState({
currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1, currentZoom: $zoomImageWheelState.currentZoom === 1 ? 2 : 1,
}); });
@ -120,7 +120,7 @@
if (state.currentZoom > 1 && isWebCompatibleImage(asset) && !hasZoomed && !$alwaysLoadOriginalFile) { if (state.currentZoom > 1 && isWebCompatibleImage(asset) && !hasZoomed && !$alwaysLoadOriginalFile) {
hasZoomed = true; hasZoomed = true;
loadAssetData({ loadOriginal: true }); handlePromiseError(loadAssetData({ loadOriginal: true }));
} }
}); });
</script> </script>

View file

@ -20,7 +20,7 @@
video.muted = false; video.muted = false;
dispatch('onVideoStarted'); dispatch('onVideoStarted');
} catch (error) { } catch (error) {
await handleError(error, 'Unable to play video'); handleError(error, 'Unable to play video');
} finally { } finally {
isVideoLoading = false; isVideoLoading = false;
} }

View file

@ -49,7 +49,7 @@
if (assetType === AssetTypeEnum.Image) { if (assetType === AssetTypeEnum.Image) {
image = $photoViewer; image = $photoViewer;
} else if (assetType === AssetTypeEnum.Video) { } else if (assetType === AssetTypeEnum.Video) {
const data = await getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp); const data = getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
const img: HTMLImageElement = new Image(); const img: HTMLImageElement = new Image();
img.src = data; img.src = data;

View file

@ -43,10 +43,10 @@
dispatch('back'); dispatch('back');
}; };
const handleSwapPeople = () => { const handleSwapPeople = async () => {
[person, selectedPeople[0]] = [selectedPeople[0], person]; [person, selectedPeople[0]] = [selectedPeople[0], person];
$page.url.searchParams.set(QueryParameter.ACTION, ActionQueryParameterValue.MERGE); $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) => { const onSelect = (selected: PersonResponseDto) => {

View file

@ -3,7 +3,7 @@
import { timeBeforeShowLoadingSpinner } from '$lib/constants'; import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { boundingBoxesArray } from '$lib/stores/people.store'; import { boundingBoxesArray } from '$lib/stores/people.store';
import { websocketEvents } from '$lib/stores/websocket'; 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 { handleError } from '$lib/utils/handle-error';
import { getPersonNameWithHiddenValue } from '$lib/utils/person'; import { getPersonNameWithHiddenValue } from '$lib/utils/person';
import { import {
@ -85,7 +85,7 @@
}; };
onMount(() => { onMount(() => {
loadPeople(); handlePromiseError(loadPeople());
return websocketEvents.on('on_person_thumbnail', onPersonThumbnail); return websocketEvents.on('on_person_thumbnail', onPersonThumbnail);
}); });
@ -164,7 +164,7 @@
} }
}; };
const handlePersonPicker = async (index: number) => { const handlePersonPicker = (index: number) => {
editedPersonIndex = index; editedPersonIndex = index;
showSeletecFaces = true; showSeletecFaces = true;
}; };

View file

@ -132,9 +132,7 @@
title={'Assign selected assets to a new person'} title={'Assign selected assets to a new person'}
size={'sm'} size={'sm'}
disabled={disableButtons || hasSelection} disabled={disableButtons || hasSelection}
on:click={() => { on:click={handleCreate}
handleCreate();
}}
> >
{#if !showLoadingSpinnerCreate} {#if !showLoadingSpinnerCreate}
<Icon path={mdiPlus} size={18} /> <Icon path={mdiPlus} size={18} />
@ -147,9 +145,7 @@
size={'sm'} size={'sm'}
title={'Assign selected assets to an existing person'} title={'Assign selected assets to an existing person'}
disabled={disableButtons || !hasSelection} disabled={disableButtons || !hasSelection}
on:click={() => { on:click={handleReassign}
handleReassign();
}}
> >
{#if !showLoadingSpinnerReassign} {#if !showLoadingSpinnerReassign}
<div> <div>

View file

@ -37,7 +37,7 @@
dispatch('submit', { library, type: LibraryType.External }); dispatch('submit', { library, type: LibraryType.External });
}; };
const handleAddExclusionPattern = async () => { const handleAddExclusionPattern = () => {
if (!addExclusionPattern) { if (!addExclusionPattern) {
return; return;
} }
@ -60,7 +60,7 @@
} }
}; };
const handleEditExclusionPattern = async () => { const handleEditExclusionPattern = () => {
if (editExclusionPattern === null) { if (editExclusionPattern === null) {
return; return;
} }
@ -79,7 +79,7 @@
} }
}; };
const handleDeleteExclusionPattern = async () => { const handleDeleteExclusionPattern = () => {
if (editExclusionPattern === null) { if (editExclusionPattern === null) {
return; return;
} }

View file

@ -47,7 +47,7 @@
return; return;
} }
} catch (error) { } catch (error) {
await handleError(error, 'Unable to connect!'); handleError(error, 'Unable to connect!');
} }
oauthLoading = false; oauthLoading = false;

View file

@ -8,7 +8,7 @@
import { AppRoute, QueryParameter } from '$lib/constants'; import { AppRoute, QueryParameter } from '$lib/constants';
import type { Viewport } from '$lib/stores/assets.store'; import type { Viewport } from '$lib/stores/assets.store';
import { memoryStore } from '$lib/stores/memory.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 { fromLocalDateTime } from '$lib/utils/timeline-util';
import { ThumbnailFormat, getMemoryLane } from '@immich/sdk'; import { ThumbnailFormat, getMemoryLane } from '@immich/sdk';
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js'; import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
@ -59,30 +59,30 @@
let paused = false; let paused = false;
// Play or pause progress when the paused state changes. // 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. // Progress should be paused when it's no longer possible to advance.
$: paused ||= !canGoForward || galleryInView; $: paused ||= !canGoForward || galleryInView;
// Advance to the next asset or memory when progress is complete. // 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 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. // 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) { if (e.key === 'ArrowRight' && canGoForward) {
e.preventDefault(); e.preventDefault();
toNext(); await toNext();
} else if (e.key === 'ArrowLeft' && canGoBack) { } else if (e.key === 'ArrowLeft' && canGoBack) {
e.preventDefault(); e.preventDefault();
toPrevious(); await toPrevious();
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
e.preventDefault(); e.preventDefault();
goto(AppRoute.PHOTOS); await goto(AppRoute.PHOTOS);
} }
}; };

View file

@ -27,7 +27,8 @@
showAlbumPicker = false; showAlbumPicker = false;
const assetIds = [...getAssets()].map((asset) => asset.id); const assetIds = [...getAssets()].map((asset) => asset.id);
createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => { createAlbum({ createAlbumDto: { albumName, assetIds } })
.then(async (response) => {
const { id, albumName } = response; const { id, albumName } = response;
notificationController.show({ notificationController.show({
@ -37,7 +38,10 @@
clearSelect(); clearSelect();
goto(`${AppRoute.ALBUMS}/${id}`); await goto(`${AppRoute.ALBUMS}/${id}`);
})
.catch((error) => {
console.error(`[add-to-album.svelte]:handleAddToNewAlbum ${error}`, error);
}); });
}; };

View file

@ -80,13 +80,17 @@
}); });
} }
const assetClickHandler = (asset: AssetResponseDto, assetsInDateGroup: AssetResponseDto[], groupTitle: string) => { const assetClickHandler = async (
asset: AssetResponseDto,
assetsInDateGroup: AssetResponseDto[],
groupTitle: string,
) => {
if (isSelectionMode || $isMultiSelectState) { if (isSelectionMode || $isMultiSelectState) {
assetSelectHandler(asset, assetsInDateGroup, groupTitle); assetSelectHandler(asset, assetsInDateGroup, groupTitle);
return; return;
} }
assetViewingStore.setAssetId(asset.id); await assetViewingStore.setAssetId(asset.id);
}; };
const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets }); const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });

View file

@ -21,6 +21,7 @@
import ShowShortcuts from '../shared-components/show-shortcuts.svelte'; import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
import AssetDateGroup from './asset-date-group.svelte'; import AssetDateGroup from './asset-date-group.svelte';
import DeleteAssetDialog from './delete-asset-dialog.svelte'; import DeleteAssetDialog from './delete-asset-dialog.svelte';
import { handlePromiseError } from '$lib/utils';
export let isSelectionMode = false; export let isSelectionMode = false;
export let singleSelect = false; export let singleSelect = false;
@ -47,19 +48,19 @@
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0; $: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
$: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id); $: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id);
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>(); const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
const onKeydown = (event: KeyboardEvent) => handlePromiseError(handleKeyboardPress(event));
onMount(async () => { onMount(async () => {
showSkeleton = false; showSkeleton = false;
document.addEventListener('keydown', onKeyboardPress); document.addEventListener('keydown', onKeydown);
assetStore.connect(); assetStore.connect();
await assetStore.init(viewport); await assetStore.init(viewport);
}); });
onDestroy(() => { onDestroy(() => {
if (browser) { if (browser) {
document.removeEventListener('keydown', onKeyboardPress); document.removeEventListener('keydown', onKeydown);
} }
if ($showAssetViewer) { if ($showAssetViewer) {
@ -69,13 +70,13 @@
assetStore.disconnect(); assetStore.disconnect();
}); });
const trashOrDelete = (force: boolean = false) => { const trashOrDelete = async (force: boolean = false) => {
isShowDeleteConfirmation = false; isShowDeleteConfirmation = false;
deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets); await deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets);
assetInteractionStore.clearMultiselect(); assetInteractionStore.clearMultiselect();
}; };
const handleKeyboardPress = (event: KeyboardEvent) => { const handleKeyboardPress = async (event: KeyboardEvent) => {
if ($isSearchEnabled || shouldIgnoreShortcut(event)) { if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
return; return;
} }
@ -98,7 +99,7 @@
} }
case '/': { case '/': {
event.preventDefault(); event.preventDefault();
goto(AppRoute.EXPLORE); await goto(AppRoute.EXPLORE);
return; return;
} }
case 'Delete': { case 'Delete': {
@ -112,7 +113,7 @@
force = true; force = true;
} }
trashOrDelete(force); await trashOrDelete(force);
} }
return; return;
} }
@ -126,12 +127,12 @@
} }
}; };
function intersectedHandler(event: CustomEvent) { async function intersectedHandler(event: CustomEvent) {
const element_ = event.detail.container as HTMLElement; const element_ = event.detail.container as HTMLElement;
const target = element_.firstChild as HTMLElement; const target = element_.firstChild as HTMLElement;
if (target) { if (target) {
const bucketDate = target.id.split('_')[1]; 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 handlePrevious = async () => {
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id); const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
if (previousAsset) { if (previousAsset) {
assetViewingStore.setAssetId(previousAsset); await assetViewingStore.setAssetId(previousAsset);
} }
return !!previousAsset; return !!previousAsset;
@ -151,7 +152,7 @@
const handleNext = async () => { const handleNext = async () => {
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id); const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
if (nextAsset) { if (nextAsset) {
assetViewingStore.setAssetId(nextAsset); await assetViewingStore.setAssetId(nextAsset);
} }
return !!nextAsset; return !!nextAsset;
@ -369,7 +370,7 @@
<DeleteAssetDialog <DeleteAssetDialog
size={idsSelectedAssets.length} size={idsSelectedAssets.length}
on:cancel={() => (isShowDeleteConfirmation = false)} on:cancel={() => (isShowDeleteConfirmation = false)}
on:confirm={() => trashOrDelete(true)} on:confirm={() => handlePromiseError(trashOrDelete(true))}
/> />
{/if} {/if}

View file

@ -2,7 +2,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; 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 { 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';
@ -29,7 +29,7 @@
dragAndDropFilesStore.subscribe((value) => { dragAndDropFilesStore.subscribe((value) => {
if (value.isDragging && value.files.length > 0) { if (value.isDragging && value.files.length > 0) {
handleUploadAssets(value.files); handlePromiseError(handleUploadAssets(value.files));
dragAndDropFilesStore.set({ isDragging: false, files: [] }); dragAndDropFilesStore.set({ isDragging: false, files: [] });
} }
}); });
@ -59,7 +59,7 @@
type: NotificationType.Info, type: NotificationType.Info,
}); });
} catch (error) { } catch (error) {
await handleError(error, 'Unable to add assets to shared link'); handleError(error, 'Unable to add assets to shared link');
} }
}; };

View file

@ -2,7 +2,7 @@
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { Theme } from '$lib/constants'; import { Theme } from '$lib/constants';
import { colorTheme, mapSettings } from '$lib/stores/preferences.store'; 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 { getMapStyle, MapTheme, type MapMarkerResponseDto } from '@immich/sdk';
import { mdiCog, mdiMapMarker } from '@mdi/js'; import { mdiCog, mdiMapMarker } from '@mdi/js';
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson'; import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
@ -152,9 +152,7 @@
applyToClusters applyToClusters
asButton asButton
let:feature let:feature
on:click={(event) => { on:click={(event) => handlePromiseError(handleClusterClick(event.detail.feature.properties.cluster_id, map))}
handleClusterClick(event.detail.feature.properties.cluster_id, map);
}}
> >
<div <div
class="rounded-full w-[40px] h-[40px] bg-immich-primary text-immich-gray flex justify-center items-center font-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90" class="rounded-full w-[40px] h-[40px] bg-immich-primary text-immich-gray flex justify-center items-center font-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90"

View file

@ -8,8 +8,8 @@
easing: cubicOut, easing: cubicOut,
}); });
onMount(() => { onMount(async () => {
progress.set(90); await progress.set(90);
}); });
</script> </script>

View file

@ -1,4 +1,5 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { handlePromiseError } from '$lib/utils';
import { tick } from 'svelte'; import { tick } from 'svelte';
/** /**
@ -36,7 +37,7 @@
} }
} }
update(target); handlePromiseError(update(target));
return { return {
update, update,
destroy, destroy,

View file

@ -6,6 +6,8 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { handlePromiseError } from '$lib/utils';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { tweened } from 'svelte/motion'; import { tweened } from 'svelte/motion';
@ -24,14 +26,14 @@
export let duration = 5; export let duration = 5;
const onChange = () => { const onChange = async () => {
progress = setDuration(duration); progress = setDuration(duration);
play(); await play();
}; };
let progress = setDuration(duration); let progress = setDuration(duration);
$: duration, onChange(); $: duration, handlePromiseError(onChange());
$: { $: {
if ($progress === 1) { if ($progress === 1) {
@ -45,35 +47,35 @@
paused: void; paused: void;
}>(); }>();
onMount(() => { onMount(async () => {
if (autoplay) { if (autoplay) {
play(); await play();
} }
}); });
export const play = () => { export const play = async () => {
status = ProgressBarStatus.Playing; status = ProgressBarStatus.Playing;
dispatch('playing'); dispatch('playing');
progress.set(1); await progress.set(1);
}; };
export const pause = () => { export const pause = async () => {
status = ProgressBarStatus.Paused; status = ProgressBarStatus.Paused;
dispatch('paused'); dispatch('paused');
progress.set($progress); await progress.set($progress);
}; };
export const restart = (autoplay: boolean) => { export const restart = async (autoplay: boolean) => {
progress.set(0); await progress.set(0);
if (autoplay) { if (autoplay) {
play(); await play();
} }
}; };
export const reset = () => { export const reset = async () => {
status = ProgressBarStatus.Paused; status = ProgressBarStatus.Paused;
progress.set(0); await progress.set(0);
}; };
function setDuration(newDuration: number) { function setDuration(newDuration: number) {

View file

@ -10,6 +10,7 @@
import SearchFilterBox from './search-filter-box.svelte'; import SearchFilterBox from './search-filter-box.svelte';
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk'; import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
import { getMetadataSearchQuery } from '$lib/utils/metadata-search'; import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
import { handlePromiseError } from '$lib/utils';
export let value = ''; export let value = '';
export let grayTheme: boolean; export let grayTheme: boolean;
@ -21,13 +22,13 @@
let showFilter = false; let showFilter = false;
$: showClearIcon = value.length > 0; $: showClearIcon = value.length > 0;
const onSearch = (payload: SmartSearchDto | MetadataSearchDto) => { const onSearch = async (payload: SmartSearchDto | MetadataSearchDto) => {
const params = getMetadataSearchQuery(payload); const params = getMetadataSearchQuery(payload);
showHistory = false; showHistory = false;
showFilter = false; showFilter = false;
$isSearchEnabled = false; $isSearchEnabled = false;
goto(`${AppRoute.SEARCH}?${params}`); await goto(`${AppRoute.SEARCH}?${params}`);
}; };
const clearSearchTerm = (searchTerm: string) => { const clearSearchTerm = (searchTerm: string) => {
@ -63,9 +64,9 @@
showFilter = false; showFilter = false;
}; };
const onHistoryTermClick = (searchTerm: string) => { const onHistoryTermClick = async (searchTerm: string) => {
const searchPayload = { query: searchTerm }; const searchPayload = { query: searchTerm };
onSearch(searchPayload); await onSearch(searchPayload);
}; };
const onFilterClick = () => { const onFilterClick = () => {
@ -78,7 +79,7 @@
}; };
const onSubmit = () => { const onSubmit = () => {
onSearch({ query: value }); handlePromiseError(onSearch({ query: value }));
saveSearchTerm(value); saveSearchTerm(value);
}; };
</script> </script>
@ -141,7 +142,7 @@
<SearchHistoryBox <SearchHistoryBox
on:clearAllSearchTerms={clearAllSearchTerms} on:clearAllSearchTerms={clearAllSearchTerms}
on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)} on:clearSearchTerm={({ detail: searchTerm }) => clearSearchTerm(searchTerm)}
on:selectSearchTerm={({ detail: searchTerm }) => onHistoryTermClick(searchTerm)} on:selectSearchTerm={({ detail: searchTerm }) => handlePromiseError(onHistoryTermClick(searchTerm))}
/> />
{/if} {/if}
</form> </form>

View file

@ -8,6 +8,7 @@
<script lang="ts"> <script lang="ts">
import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk'; import { SearchSuggestionType, getSearchSuggestions } from '@immich/sdk';
import Combobox, { toComboBoxOptions } from '../combobox.svelte'; import Combobox, { toComboBoxOptions } from '../combobox.svelte';
import { handlePromiseError } from '$lib/utils';
export let filters: SearchCameraFilter; export let filters: SearchCameraFilter;
@ -16,8 +17,8 @@
$: makeFilter = filters.make; $: makeFilter = filters.make;
$: modelFilter = filters.model; $: modelFilter = filters.model;
$: updateMakes(modelFilter); $: handlePromiseError(updateMakes(modelFilter));
$: updateModels(makeFilter); $: handlePromiseError(updateModels(makeFilter));
async function updateMakes(model?: string) { async function updateMakes(model?: string) {
makes = await getSearchSuggestions({ makes = await getSearchSuggestions({

View file

@ -82,7 +82,7 @@
}; };
}; };
const search = async () => { const search = () => {
if (filter.context && filter.personIds.size > 0) { if (filter.context && filter.personIds.size > 0) {
handleError( handleError(
new Error('Context search does not support people filter'), new Error('Context search does not support people filter'),

View file

@ -9,6 +9,7 @@
<script lang="ts"> <script lang="ts">
import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk'; import { getSearchSuggestions, SearchSuggestionType } from '@immich/sdk';
import Combobox, { toComboBoxOptions } from '../combobox.svelte'; import Combobox, { toComboBoxOptions } from '../combobox.svelte';
import { handlePromiseError } from '$lib/utils';
export let filters: SearchLocationFilter; export let filters: SearchLocationFilter;
@ -18,9 +19,9 @@
$: countryFilter = filters.country; $: countryFilter = filters.country;
$: stateFilter = filters.state; $: stateFilter = filters.state;
$: updateCountries(); $: handlePromiseError(updateCountries());
$: updateStates(countryFilter); $: handlePromiseError(updateStates(countryFilter));
$: updateCities(countryFilter, stateFilter); $: handlePromiseError(updateCities(countryFilter, stateFilter));
async function updateCountries() { async function updateCountries() {
countries = await getSearchSuggestions({ countries = await getSearchSuggestions({

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
import { QueryParameter } from '$lib/constants'; import { QueryParameter } from '$lib/constants';
import { hasParamValue, updateParamList } from '$lib/utils'; import { hasParamValue, handlePromiseError, updateParamList } from '$lib/utils';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
export let title: string; export let title: string;
@ -12,12 +12,12 @@
const syncFromUrl = () => (isOpen = hasParamValue(QueryParameter.IS_OPEN, key)); const syncFromUrl = () => (isOpen = hasParamValue(QueryParameter.IS_OPEN, key));
const syncToUrl = (isOpen: boolean) => updateParamList({ param: QueryParameter.IS_OPEN, value: key, add: isOpen }); const syncToUrl = (isOpen: boolean) => updateParamList({ param: QueryParameter.IS_OPEN, value: key, add: isOpen });
isOpen ? syncToUrl(true) : syncFromUrl(); isOpen ? handlePromiseError(syncToUrl(true)) : syncFromUrl();
$: $page.url && syncFromUrl(); $: $page.url && syncFromUrl();
const toggle = () => { const toggle = async () => {
isOpen = !isOpen; isOpen = !isOpen;
syncToUrl(isOpen); await syncToUrl(isOpen);
}; };
</script> </script>

View file

@ -13,9 +13,9 @@
export let uploadAsset: UploadAsset; export let uploadAsset: UploadAsset;
const handleRetry = (uploadAsset: UploadAsset) => { const handleRetry = async (uploadAsset: UploadAsset) => {
uploadAssetsStore.removeUploadAsset(uploadAsset.id); uploadAssetsStore.removeUploadAsset(uploadAsset.id);
fileUploadHandler([uploadAsset.file], uploadAsset.albumId); await fileUploadHandler([uploadAsset.file], uploadAsset.albumId);
}; };
</script> </script>

View file

@ -56,8 +56,8 @@
let selectedLibraryIndex = 0; let selectedLibraryIndex = 0;
let selectedLibrary: LibraryResponseDto | null = null; let selectedLibrary: LibraryResponseDto | null = null;
onMount(() => { onMount(async () => {
readLibraryList(); await readLibraryList();
}); });
const closeAll = () => { const closeAll = () => {
@ -234,11 +234,11 @@
updateLibraryIndex = selectedLibraryIndex; updateLibraryIndex = selectedLibraryIndex;
}; };
const onScanNewLibraryClicked = () => { const onScanNewLibraryClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary) { if (selectedLibrary) {
handleScan(selectedLibrary.id); await handleScan(selectedLibrary.id);
} }
}; };
@ -248,38 +248,38 @@
updateLibraryIndex = selectedLibraryIndex; updateLibraryIndex = selectedLibraryIndex;
}; };
const onScanAllLibraryFilesClicked = () => { const onScanAllLibraryFilesClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary) { if (selectedLibrary) {
handleScanChanges(selectedLibrary.id); await handleScanChanges(selectedLibrary.id);
} }
}; };
const onForceScanAllLibraryFilesClicked = () => { const onForceScanAllLibraryFilesClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary) { if (selectedLibrary) {
handleForceScan(selectedLibrary.id); await handleForceScan(selectedLibrary.id);
} }
}; };
const onRemoveOfflineFilesClicked = () => { const onRemoveOfflineFilesClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary) { if (selectedLibrary) {
handleRemoveOffline(selectedLibrary.id); await handleRemoveOffline(selectedLibrary.id);
} }
}; };
const onDeleteLibraryClicked = () => { const onDeleteLibraryClicked = async () => {
closeAll(); closeAll();
if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) { if (selectedLibrary && confirm(`Are you sure you want to delete ${selectedLibrary.name} library?`) == true) {
refreshStats(selectedLibraryIndex); await refreshStats(selectedLibraryIndex);
if (totalCount[selectedLibraryIndex] > 0) { if (totalCount[selectedLibraryIndex] > 0) {
deleteAssetCount = totalCount[selectedLibraryIndex]; deleteAssetCount = totalCount[selectedLibraryIndex];
confirmDeleteLibrary = selectedLibrary; confirmDeleteLibrary = selectedLibrary;
} else { } else {
deletedLibrary = selectedLibrary; deletedLibrary = selectedLibrary;
handleDelete(); await handleDelete();
} }
} }
}; };
@ -348,27 +348,27 @@
{#if showContextMenu} {#if showContextMenu}
<Portal target="body"> <Portal target="body">
<ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}> <ContextMenu {...contextMenuPosition} on:outclick={onMenuExit}>
<MenuOption on:click={() => onRenameClicked()} text={`Rename`} /> <MenuOption on:click={onRenameClicked} text={`Rename`} />
{#if selectedLibrary && selectedLibrary.type === LibraryType.External} {#if selectedLibrary && selectedLibrary.type === LibraryType.External}
<MenuOption on:click={() => onEditImportPathClicked()} text="Edit Import Paths" /> <MenuOption on:click={onEditImportPathClicked} text="Edit Import Paths" />
<MenuOption on:click={() => onScanSettingClicked()} text="Scan Settings" /> <MenuOption on:click={onScanSettingClicked} text="Scan Settings" />
<hr /> <hr />
<MenuOption on:click={() => onScanNewLibraryClicked()} text="Scan New Library Files" /> <MenuOption on:click={onScanNewLibraryClicked} text="Scan New Library Files" />
<MenuOption <MenuOption
on:click={() => onScanAllLibraryFilesClicked()} on:click={onScanAllLibraryFilesClicked}
text="Re-scan All Library Files" text="Re-scan All Library Files"
subtitle={'Only refreshes modified files'} subtitle={'Only refreshes modified files'}
/> />
<MenuOption <MenuOption
on:click={() => onForceScanAllLibraryFilesClicked()} on:click={onForceScanAllLibraryFilesClicked}
text="Force Re-scan All Library Files" text="Force Re-scan All Library Files"
subtitle={'Refreshes every file'} subtitle={'Refreshes every file'}
/> />
<hr /> <hr />
<MenuOption on:click={() => onRemoveOfflineFilesClicked()} text="Remove Offline Files" /> <MenuOption on:click={onRemoveOfflineFilesClicked} text="Remove Offline Files" />
<MenuOption on:click={() => onDeleteLibraryClicked()}> <MenuOption on:click={onDeleteLibraryClicked}>
<p class="text-red-600">Delete library</p> <p class="text-red-600">Delete library</p>
</MenuOption> </MenuOption>
{/if} {/if}

View file

@ -28,7 +28,7 @@
} catch (error) { } catch (error) {
handleError(error, 'Unable to link OAuth account'); handleError(error, 'Unable to link OAuth account');
} finally { } finally {
goto('?open=oauth'); await goto('?open=oauth');
} }
} }

View file

@ -31,8 +31,8 @@
let removePartnerDto: PartnerResponseDto | null = null; let removePartnerDto: PartnerResponseDto | null = null;
let partners: Array<PartnerSharing> = []; let partners: Array<PartnerSharing> = [];
onMount(() => { onMount(async () => {
refreshPartners(); await refreshPartners();
}); });
const refreshPartners = async () => { const refreshPartners = async () => {

View file

@ -172,15 +172,17 @@ export class AssetStore {
this.emit(false); this.emit(false);
let height = 0; let height = 0;
const loaders = [];
for (const bucket of this.buckets) { for (const bucket of this.buckets) {
if (height < viewport.height) { if (height < viewport.height) {
height += bucket.bucketHeight; height += bucket.bucketHeight;
this.loadBucket(bucket.bucketDate, BucketPosition.Visible); loaders.push(this.loadBucket(bucket.bucketDate, BucketPosition.Visible));
continue; continue;
} }
break; break;
} }
await Promise.all(loaders);
} }
async loadBucket(bucketDate: string, position: BucketPosition): Promise<void> { async loadBucket(bucketDate: string, position: BucketPosition): Promise<void> {

View file

@ -47,7 +47,7 @@ websocket
.on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion)) .on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
.on('connect_error', (e) => console.log('Websocket Connect Error', e)); .on('connect_error', (e) => console.log('Websocket Connect Error', e));
export const openWebsocketConnection = async () => { export const openWebsocketConnection = () => {
try { try {
if (!get(user)) { if (!get(user)) {
return; return;

View file

@ -194,3 +194,13 @@ export const findLocale = (code: string | undefined) => {
name: language?.name, name: language?.name,
}; };
}; };
export const asyncTimeout = (ms: number) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
export const handlePromiseError = <T>(promise: Promise<T>): void => {
promise.catch((error) => console.error(`[utils.ts]:handlePromiseError ${error}`, error));
};

View file

@ -28,10 +28,14 @@ describe('Executor Queue test', function () {
}); });
// The first 3 should be finished within 200ms (concurrency 3) // The first 3 should be finished within 200ms (concurrency 3)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
eq.addTask(() => timeoutPromiseBuilder(100, 'T1')); eq.addTask(() => timeoutPromiseBuilder(100, 'T1'));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
eq.addTask(() => timeoutPromiseBuilder(200, 'T2')); eq.addTask(() => timeoutPromiseBuilder(200, 'T2'));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
eq.addTask(() => timeoutPromiseBuilder(150, 'T3')); eq.addTask(() => timeoutPromiseBuilder(150, 'T3'));
// The last task will be executed after 200ms and will finish at 400ms // 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')); eq.addTask(() => timeoutPromiseBuilder(200, 'T4'));
expect(finished).not.toBeCalled(); expect(finished).not.toBeCalled();

View file

@ -1,3 +1,5 @@
import { handlePromiseError } from '$lib/utils';
interface Options { interface Options {
concurrency: number; concurrency: number;
} }
@ -66,6 +68,6 @@ export class ExecutorQueue {
return; return;
} }
runnable(); handlePromiseError(runnable());
} }
} }

View file

@ -29,7 +29,7 @@ export const openFileUploadDialog = async (albumId?: string | undefined) => {
fileSelector.type = 'file'; fileSelector.type = 'file';
fileSelector.multiple = true; fileSelector.multiple = true;
fileSelector.accept = extensions.join(','); fileSelector.accept = extensions.join(',');
fileSelector.addEventListener('change', async (e: Event) => { fileSelector.addEventListener('change', (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
if (!target.files) { if (!target.files) {
return; return;
@ -119,7 +119,7 @@ async function fileUploader(asset: File, albumId: string | undefined = undefined
} }
}) })
.catch(async (error) => { .catch(async (error) => {
await handleError(error, 'Unable to upload file'); handleError(error, 'Unable to upload file');
const reason = (await getServerErrorMessage(error)) || error; const reason = (await getServerErrorMessage(error)) || error;
uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason }); uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason });
return undefined; return undefined;

View file

@ -22,14 +22,15 @@ 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') { if ((error as Error)?.name === 'AbortError') {
return; return;
} }
console.error(`[handleError]: ${message}`, error, (error as Error)?.stack); console.error(`[handleError]: ${message}`, error, (error as Error)?.stack);
let serverMessage = await getServerErrorMessage(error); getServerErrorMessage(error)
.then((serverMessage) => {
if (serverMessage) { if (serverMessage) {
serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`; serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
} }
@ -38,4 +39,8 @@ export async function handleError(error: unknown, message: string) {
message: serverMessage || message, message: serverMessage || message,
type: NotificationType.Error, type: NotificationType.Error,
}); });
})
.catch((error) => {
console.error(error);
});
} }

View file

@ -196,7 +196,7 @@
const handleCreateAlbum = async () => { const handleCreateAlbum = async () => {
const newAlbum = await createAlbum(); const newAlbum = await createAlbum();
if (newAlbum) { 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); return new Date(dateString).toLocaleDateString($locale, dateFormats.album);
}; };
onMount(() => { onMount(async () => {
removeAlbumsIfEmpty(); await removeAlbumsIfEmpty();
}); });
const removeAlbumsIfEmpty = async () => { const removeAlbumsIfEmpty = async () => {

View file

@ -220,12 +220,11 @@
onMount(async () => { onMount(async () => {
if (album.sharedUsers.length > 0) { if (album.sharedUsers.length > 0) {
getFavorite(); await Promise.all([getFavorite(), getNumberOfComments()]);
getNumberOfComments();
} }
}); });
const handleKeypress = async (event: KeyboardEvent) => { const handleKeypress = (event: KeyboardEvent) => {
if (event.target !== textArea) { if (event.target !== textArea) {
return; return;
} }
@ -242,12 +241,12 @@
const handleStartSlideshow = async () => { const handleStartSlideshow = async () => {
const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0];
if (asset) { if (asset) {
setAssetId(asset.id); await setAssetId(asset.id);
$slideshowState = SlideshowState.PlaySlideshow; $slideshowState = SlideshowState.PlaySlideshow;
} }
}; };
const handleEscape = () => { const handleEscape = async () => {
if (viewMode === ViewMode.SELECT_USERS) { if (viewMode === ViewMode.SELECT_USERS) {
viewMode = ViewMode.VIEW; viewMode = ViewMode.VIEW;
return; return;
@ -275,7 +274,7 @@
assetInteractionStore.clearMultiselect(); assetInteractionStore.clearMultiselect();
return; return;
} }
goto(backUrl); await goto(backUrl);
return; return;
}; };
@ -371,7 +370,7 @@
const handleRemoveUser = async (userId: string) => { const handleRemoveUser = async (userId: string) => {
if (userId == 'me' || userId === $user.id) { if (userId == 'me' || userId === $user.id) {
goto(backUrl); await goto(backUrl);
return; return;
} }
@ -390,7 +389,7 @@
const handleRemoveAlbum = async () => { const handleRemoveAlbum = async () => {
try { try {
await deleteAlbum({ id: album.id }); await deleteAlbum({ id: album.id });
goto(backUrl); await goto(backUrl);
} catch (error) { } catch (error) {
handleError(error, 'Unable to delete album'); handleError(error, 'Unable to delete album');
} finally { } finally {

View file

@ -2,7 +2,7 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params }) => { export const load: PageLoad = ({ params }) => {
const albumId = params.albumId; const albumId = params.albumId;
if (albumId) { if (albumId) {

View file

@ -1,5 +1,6 @@
import type { OnShowContextMenuDetail } from '$lib/components/album-page/album-card'; import type { OnShowContextMenuDetail } from '$lib/components/album-page/album-card';
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
import { asyncTimeout } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { createAlbum, deleteAlbum, getAllAlbums, type AlbumResponseDto } from '@immich/sdk'; import { createAlbum, deleteAlbum, getAllAlbums, type AlbumResponseDto } from '@immich/sdk';
import { derived, get, writable } from 'svelte/store'; 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 '' // Delete album that has no photos and is named ''
for (const album of data) { for (const album of data) {
if (album.albumName === '' && album.assetCount === 0) { if (album.albumName === '' && album.assetCount === 0) {
setTimeout(async () => { await asyncTimeout(500);
await handleDeleteAlbum(album); await handleDeleteAlbum(album);
}, 500);
} }
} }
} catch { } catch {
@ -46,10 +46,7 @@ export const useAlbums = (properties: AlbumsProperties) => {
albums.set(get(albums).filter(({ id }) => id !== albumToDelete.id)); albums.set(get(albums).filter(({ id }) => id !== albumToDelete.id));
} }
async function showAlbumContextMenu( function showAlbumContextMenu(contextMenuDetail: OnShowContextMenuDetail, album: AlbumResponseDto): void {
contextMenuDetail: OnShowContextMenuDetail,
album: AlbumResponseDto,
): Promise<void> {
contextMenuTargetAlbum.set(album); contextMenuTargetAlbum.set(album);
contextMenuPosition.set({ contextMenuPosition.set({

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load: PageLoad = async () => { export const load: PageLoad = () => {
redirect(302, AppRoute.ARCHIVE); redirect(302, AppRoute.ARCHIVE);
}; };

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load: PageLoad = async () => { export const load: PageLoad = () => {
redirect(302, AppRoute.FAVORITES); redirect(302, AppRoute.FAVORITES);
}; };

View file

@ -15,6 +15,7 @@
import { DateTime, Duration } from 'luxon'; import { DateTime, Duration } from 'luxon';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { handlePromiseError } from '$lib/utils';
export let data: PageData; export let data: PageData;
@ -26,8 +27,8 @@
let viewingAssetCursor = 0; let viewingAssetCursor = 0;
let showSettingsModal = false; let showSettingsModal = false;
onMount(() => { onMount(async () => {
loadMapMarkers().then((data) => (mapMarkers = data)); mapMarkers = await loadMapMarkers();
}); });
onDestroy(() => { onDestroy(() => {
@ -35,7 +36,7 @@
assetViewingStore.showAssetViewer(false); assetViewingStore.showAssetViewer(false);
}); });
$: $featureFlags.map || goto(AppRoute.PHOTOS); $: $featureFlags.map || handlePromiseError(goto(AppRoute.PHOTOS));
const omit = (obj: MapSettings, key: string) => { const omit = (obj: MapSettings, key: string) => {
return Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key)); return Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key));
}; };
@ -85,21 +86,21 @@
} }
} }
function onViewAssets(assetIds: string[]) { async function onViewAssets(assetIds: string[]) {
assetViewingStore.setAssetId(assetIds[0]); await assetViewingStore.setAssetId(assetIds[0]);
viewingAssets = assetIds; viewingAssets = assetIds;
viewingAssetCursor = 0; viewingAssetCursor = 0;
} }
function navigateNext() { async function navigateNext() {
if (viewingAssetCursor < viewingAssets.length - 1) { if (viewingAssetCursor < viewingAssets.length - 1) {
assetViewingStore.setAssetId(viewingAssets[++viewingAssetCursor]); await assetViewingStore.setAssetId(viewingAssets[++viewingAssetCursor]);
} }
} }
function navigatePrevious() { async function navigatePrevious() {
if (viewingAssetCursor > 0) { if (viewingAssetCursor > 0) {
assetViewingStore.setAssetId(viewingAssets[--viewingAssetCursor]); await assetViewingStore.setAssetId(viewingAssets[--viewingAssetCursor]);
} }
} }
</script> </script>

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async () => { export const load = (() => {
redirect(302, AppRoute.PHOTOS); redirect(302, AppRoute.PHOTOS);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -81,12 +81,12 @@
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
onMount(() => { onMount(async () => {
document.addEventListener('keydown', onKeyboardPress); document.addEventListener('keydown', onKeyboardPress);
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE); const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
if (getSearchedPeople) { if (getSearchedPeople) {
searchName = 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); $page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
goto($page.url); await goto($page.url);
handleSearchPeople(force); await handleSearchPeople(force);
}; };
const handleCloseClick = () => { const handleCloseClick = () => {
@ -293,8 +293,8 @@
} }
}; };
const handleMergePeople = (detail: PersonResponseDto) => { const handleMergePeople = async (detail: PersonResponseDto) => {
goto( await goto(
`${AppRoute.PEOPLE}/${detail.id}?${QueryParameter.ACTION}=${ActionQueryParameterValue.MERGE}&${QueryParameter.PREVIOUS_ROUTE}=${AppRoute.PEOPLE}`, `${AppRoute.PEOPLE}/${detail.id}?${QueryParameter.ACTION}=${ActionQueryParameterValue.MERGE}&${QueryParameter.PREVIOUS_ROUTE}=${AppRoute.PEOPLE}`,
); );
}; };
@ -303,7 +303,7 @@
if (searchName === '') { if (searchName === '') {
if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) { if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) {
$page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE); $page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE);
goto($page.url); await goto($page.url);
} }
return; return;
} }
@ -331,7 +331,7 @@
return; return;
} }
if (personName === '') { if (personName === '') {
changeName(); await changeName();
return; return;
} }
const data = await searchPerson({ name: personName, withHidden: true }); const data = await searchPerson({ name: personName, withHidden: true });
@ -359,7 +359,7 @@
.slice(0, 3); .slice(0, 3);
return; return;
} }
changeName(); await changeName();
}; };
const submitBirthDateChange = async (value: string) => { const submitBirthDateChange = async (value: string) => {

View file

@ -185,7 +185,7 @@
} }
}; };
const handleEscape = () => { const handleEscape = async () => {
if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) { if ($showAssetViewer || viewMode === ViewMode.SUGGEST_MERGE) {
return; return;
} }
@ -193,7 +193,7 @@
assetInteractionStore.clearMultiselect(); assetInteractionStore.clearMultiselect();
return; return;
} else { } else {
goto(previousRoute); await goto(previousRoute);
return; return;
} }
}; };
@ -235,7 +235,7 @@
type: NotificationType.Info, type: NotificationType.Info,
}); });
goto(previousRoute, { replaceState: true }); await goto(previousRoute, { replaceState: true });
} catch (error) { } catch (error) {
handleError(error, 'Unable to hide person'); handleError(error, 'Unable to hide person');
} }
@ -244,7 +244,7 @@
const handleMerge = async (person: PersonResponseDto) => { const handleMerge = async (person: PersonResponseDto) => {
const { assets } = await getPersonStatistics({ id: person.id }); const { assets } = await getPersonStatistics({ id: person.id });
numberOfAssets = assets; numberOfAssets = assets;
handleGoBack(); await handleGoBack();
data.person = person; data.person = person;
@ -292,7 +292,7 @@
refreshAssetGrid = !refreshAssetGrid; refreshAssetGrid = !refreshAssetGrid;
return; return;
} }
goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true }); await goto(`${AppRoute.PEOPLE}/${personToBeMergedIn.id}`, { replaceState: true });
} catch (error) { } catch (error) {
handleError(error, 'Unable to save name'); handleError(error, 'Unable to save name');
} }
@ -341,7 +341,7 @@
return; return;
} }
if (name === '') { if (name === '') {
changeName(); await changeName();
return; return;
} }
@ -366,7 +366,7 @@
viewMode = ViewMode.SUGGEST_MERGE; viewMode = ViewMode.SUGGEST_MERGE;
return; return;
} }
changeName(); await changeName();
}; };
const handleSetBirthDate = async (birthDate: string) => { const handleSetBirthDate = async (birthDate: string) => {
@ -392,11 +392,11 @@
} }
}; };
const handleGoBack = () => { const handleGoBack = async () => {
viewMode = ViewMode.VIEW_ASSETS; viewMode = ViewMode.VIEW_ASSETS;
if ($page.url.searchParams.has(QueryParameter.ACTION)) { if ($page.url.searchParams.has(QueryParameter.ACTION)) {
$page.url.searchParams.delete(QueryParameter.ACTION); $page.url.searchParams.delete(QueryParameter.ACTION);
goto($page.url); await goto($page.url);
} }
}; };
</script> </script>

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async ({ params }) => { export const load = (({ params }) => {
redirect(302, `${AppRoute.PEOPLE}/${params.personId}`); redirect(302, `${AppRoute.PEOPLE}/${params.personId}`);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async () => { export const load = (() => {
redirect(302, AppRoute.PHOTOS); redirect(302, AppRoute.PHOTOS);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -35,6 +35,7 @@
import type { Viewport } from '$lib/stores/assets.store'; import type { Viewport } from '$lib/stores/assets.store';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { handlePromiseError } from '$lib/utils';
import { parseUtcDate } from '$lib/utils/date-time'; import { parseUtcDate } from '$lib/utils/date-time';
const MAX_ASSET_COUNT = 5000; const MAX_ASSET_COUNT = 5000;
@ -53,7 +54,7 @@
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
const handleKeyboardPress = (event: KeyboardEvent) => { const handleKeyboardPress = async (event: KeyboardEvent) => {
if (shouldIgnoreShortcut(event)) { if (shouldIgnoreShortcut(event)) {
return; return;
} }
@ -65,7 +66,7 @@
return; return;
} }
if (!$preventRaceConditionSearchBar) { if (!$preventRaceConditionSearchBar) {
goto(previousRoute); await goto(previousRoute);
} }
$preventRaceConditionSearchBar = false; $preventRaceConditionSearchBar = false;
return; return;
@ -108,13 +109,13 @@
return searchQuery ? JSON.parse(searchQuery) : {}; return searchQuery ? JSON.parse(searchQuery) : {};
})(); })();
$: terms, onSearchQueryUpdate(); $: terms, handlePromiseError(onSearchQueryUpdate());
async function onSearchQueryUpdate() { async function onSearchQueryUpdate() {
nextPage = 1; nextPage = 1;
searchResultAssets = []; searchResultAssets = [];
searchResultAlbums = []; searchResultAlbums = [];
loadNextPage(); await loadNextPage();
} }
export const loadNextPage = async () => { export const loadNextPage = async () => {

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async () => { export const load = (() => {
redirect(302, AppRoute.SEARCH); redirect(302, AppRoute.SEARCH);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -19,7 +19,7 @@
const createSharedAlbum = async () => { const createSharedAlbum = async () => {
try { try {
const newAlbum = await createAlbum({ createAlbumDto: { albumName: '' } }); const newAlbum = await createAlbum({ createAlbumDto: { albumName: '' } });
goto(`${AppRoute.ALBUMS}/${newAlbum.id}`); await goto(`${AppRoute.ALBUMS}/${newAlbum.id}`);
} catch (error) { } catch (error) {
handleError(error, 'Unable to create album'); handleError(error, 'Unable to create album');
} }

View file

@ -40,7 +40,7 @@
deleteLinkId = null; deleteLinkId = null;
await refresh(); await refresh();
} catch (error) { } catch (error) {
await handleError(error, 'Unable to delete shared link'); handleError(error, 'Unable to delete shared link');
} }
}; };

View file

@ -24,10 +24,11 @@
import { emptyTrash, restoreTrash } from '@immich/sdk'; import { emptyTrash, restoreTrash } from '@immich/sdk';
import { mdiDeleteOutline, mdiHistory } from '@mdi/js'; import { mdiDeleteOutline, mdiHistory } from '@mdi/js';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { handlePromiseError } from '$lib/utils';
export let data: PageData; export let data: PageData;
$: $featureFlags.trash || goto(AppRoute.PHOTOS); $featureFlags.trash || handlePromiseError(goto(AppRoute.PHOTOS));
const assetStore = new AssetStore({ isTrashed: true }); const assetStore = new AssetStore({ isTrashed: true });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async () => { export const load = (() => {
redirect(302, AppRoute.TRASH); redirect(302, AppRoute.TRASH);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -3,7 +3,7 @@ import type { LayoutLoad } from './$types';
export const ssr = false; export const ssr = false;
export const csr = true; export const csr = true;
export const load = (async () => { export const load = (() => {
return { return {
meta: { meta: {
title: 'Immich', title: 'Immich',

View file

@ -2,6 +2,6 @@ import { AppRoute } from '$lib/constants';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
export const load = (async () => { export const load = (() => {
redirect(302, AppRoute.ADMIN_USER_MANAGEMENT); redirect(302, AppRoute.ADMIN_USER_MANAGEMENT);
}) satisfies PageLoad; }) satisfies PageLoad;

View file

@ -4,6 +4,7 @@
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { asyncTimeout } from '$lib/utils';
import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk'; import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk';
import { mdiCog } from '@mdi/js'; import { mdiCog } from '@mdi/js';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
@ -11,21 +12,19 @@
export let data: PageData; export let data: PageData;
let timer: ReturnType<typeof setInterval>;
let jobs: AllJobStatusResponseDto; let jobs: AllJobStatusResponseDto;
const load = async () => { let running = true;
jobs = await getAllJobsStatus();
};
onMount(async () => { onMount(async () => {
await load(); while (running) {
timer = setInterval(load, 5000); jobs = await getAllJobsStatus();
await asyncTimeout(5000);
}
}); });
onDestroy(() => { onDestroy(() => {
clearInterval(timer); running = false;
}); });
</script> </script>

View file

@ -4,19 +4,21 @@
import { getServerStatistics } from '@immich/sdk'; import { getServerStatistics } from '@immich/sdk';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { asyncTimeout } from '$lib/utils';
export let data: PageData; export let data: PageData;
let setIntervalHandler: ReturnType<typeof setInterval>; let running = true;
onMount(async () => { onMount(async () => {
setIntervalHandler = setInterval(async () => { while (running) {
data.stats = await getServerStatistics(); data.stats = await getServerStatistics();
}, 5000); await asyncTimeout(5000);
}
}); });
onDestroy(() => { onDestroy(() => {
clearInterval(setIntervalHandler); running = false;
}); });
</script> </script>

View file

@ -51,7 +51,7 @@
shouldShowCreateUserForm = false; shouldShowCreateUserForm = false;
}; };
const editUserHandler = async (user: UserResponseDto) => { const editUserHandler = (user: UserResponseDto) => {
selectedUser = user; selectedUser = user;
shouldShowEditUserForm = true; shouldShowEditUserForm = true;
}; };
@ -67,7 +67,7 @@
shouldShowInfoPanel = true; shouldShowInfoPanel = true;
}; };
const deleteUserHandler = async (user: UserResponseDto) => { const deleteUserHandler = (user: UserResponseDto) => {
selectedUser = user; selectedUser = user;
shouldShowDeleteConfirmDialog = true; shouldShowDeleteConfirmDialog = true;
}; };
@ -82,7 +82,7 @@
shouldShowDeleteConfirmDialog = false; shouldShowDeleteConfirmDialog = false;
}; };
const restoreUserHandler = async (user: UserResponseDto) => { const restoreUserHandler = (user: UserResponseDto) => {
selectedUser = user; selectedUser = user;
shouldShowRestoreDialog = true; shouldShowRestoreDialog = true;
}; };

View file

@ -3,10 +3,17 @@
import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte'; import ChangePasswordForm from '$lib/components/forms/change-password-form.svelte';
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
import { AppRoute } from '$lib/constants'; 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'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;
const onSuccess = async () => {
await goto(AppRoute.AUTH_LOGIN);
resetSavedUser();
await logout();
};
</script> </script>
<FullscreenContainer title={data.meta.title}> <FullscreenContainer title={data.meta.title}>
@ -18,5 +25,5 @@
enter the new password below. enter the new password below.
</p> </p>
<ChangePasswordForm user={$user} on:success={() => goto(AppRoute.AUTH_LOGIN)} /> <ChangePasswordForm user={$user} on:success={onSuccess} />
</FullscreenContainer> </FullscreenContainer>

View file

@ -1,21 +1,12 @@
<script lang="ts"> <script lang="ts">
import { afterNavigate, goto } from '$app/navigation'; import { goto } from '$app/navigation';
import LoginForm from '$lib/components/forms/login-form.svelte'; import LoginForm from '$lib/components/forms/login-form.svelte';
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte'; import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
import { resetSavedUser } from '$lib/stores/user.store';
import { logout } from '@immich/sdk';
import type { PageData } from './$types'; import type { PageData } from './$types';
export let data: PageData; export let data: PageData;
afterNavigate(async ({ from }) => {
if (from?.url?.pathname === AppRoute.AUTH_CHANGE_PASSWORD) {
resetSavedUser();
await logout();
}
});
</script> </script>
{#if $featureFlags.loaded} {#if $featureFlags.loaded}

View file

@ -29,17 +29,17 @@
const handleDoneClicked = async () => { const handleDoneClicked = async () => {
if (index >= onboardingSteps.length - 1) { if (index >= onboardingSteps.length - 1) {
await setAdminOnboarding(); await setAdminOnboarding();
goto(AppRoute.PHOTOS); await goto(AppRoute.PHOTOS);
} else { } else {
index++; 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) { if (index >= 1) {
index--; index--;
goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`); await goto(`${AppRoute.AUTH_ONBOARDING}?${QueryParameter.ONBOARDING_STEP}=${onboardingSteps[index].name}`);
} }
}; };
</script> </script>

View file

@ -4,12 +4,6 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
onwarn: (warning, handler) => {
if (warning.code.includes('a11y')) {
return;
}
handler(warning);
},
kit: { kit: {
adapter: adapter({ adapter: adapter({
// default options are shown. On some platforms // default options are shown. On some platforms