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:
parent
57f25855d3
commit
907a95a746
70 changed files with 312 additions and 282 deletions
|
@ -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
8
web/package-lock.json
generated
|
@ -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": {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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] };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
easing: cubicOut,
|
easing: cubicOut,
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
progress.set(90);
|
await progress.set(90);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue