mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-27 15:07:45 +00:00
feat: handle-error minor improvments (#25288)
* feat: handle-error minor improvments * review comments * Update web/src/lib/utils/handle-error.ts Co-authored-by: Jason Rasmussen <jason@rasm.me> --------- Co-authored-by: Alex <alex.tran1502@gmail.com> Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
parent
b669714bda
commit
280f906e4b
6 changed files with 65 additions and 42 deletions
|
|
@ -1009,9 +1009,11 @@
|
||||||
"error_getting_places": "Error getting places",
|
"error_getting_places": "Error getting places",
|
||||||
"error_loading_image": "Error loading image",
|
"error_loading_image": "Error loading image",
|
||||||
"error_loading_partners": "Error loading partners: {error}",
|
"error_loading_partners": "Error loading partners: {error}",
|
||||||
|
"error_retrieving_asset_information": "Error retrieving asset information",
|
||||||
"error_saving_image": "Error: {error}",
|
"error_saving_image": "Error: {error}",
|
||||||
"error_tag_face_bounding_box": "Error tagging face - cannot get bounding box coordinates",
|
"error_tag_face_bounding_box": "Error tagging face - cannot get bounding box coordinates",
|
||||||
"error_title": "Error - Something went wrong",
|
"error_title": "Error - Something went wrong",
|
||||||
|
"error_while_navigating": "Error while navigating to asset",
|
||||||
"errors": {
|
"errors": {
|
||||||
"cannot_navigate_next_asset": "Cannot navigate to the next asset",
|
"cannot_navigate_next_asset": "Cannot navigate to the next asset",
|
||||||
"cannot_navigate_previous_asset": "Cannot navigate to previous asset",
|
"cannot_navigate_previous_asset": "Cannot navigate to previous asset",
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@
|
||||||
await handleStopSlideshow();
|
await handleStopSlideshow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, $t('error_while_navigating'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const showEditor = () => {
|
const showEditor = () => {
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
|
import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
|
||||||
import { navigateToAsset } from '$lib/utils/asset-utils';
|
import { navigateToAsset } from '$lib/utils/asset-utils';
|
||||||
|
import { handleErrorAsync } from '$lib/utils/handle-error';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { type AlbumResponseDto, type AssetResponseDto, type PersonResponseDto, getAssetInfo } from '@immich/sdk';
|
import { type AlbumResponseDto, type AssetResponseDto, type PersonResponseDto, getAssetInfo } from '@immich/sdk';
|
||||||
import { onDestroy, onMount, untrack } from 'svelte';
|
import { onDestroy, onMount, untrack } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
let { asset: viewingAsset, gridScrollTarget } = assetViewingStore;
|
let { asset: viewingAsset, gridScrollTarget } = assetViewingStore;
|
||||||
|
|
||||||
|
|
@ -38,28 +40,27 @@
|
||||||
person,
|
person,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const getNextAsset = async (currentAsset: AssetResponseDto, preload: boolean = true) => {
|
const getAsset = (id: string) => {
|
||||||
const earlierTimelineAsset = await timelineManager.getEarlierAsset(currentAsset);
|
return handleErrorAsync(
|
||||||
if (earlierTimelineAsset) {
|
() => assetCacheManager.getAsset({ ...authManager.params, id }),
|
||||||
const asset = await assetCacheManager.getAsset({ ...authManager.params, id: earlierTimelineAsset.id });
|
$t('error_retrieving_asset_information'),
|
||||||
if (preload) {
|
);
|
||||||
// also pre-cache an extra one, to pre-cache these assetInfos for the next nav after this one is complete
|
|
||||||
void getNextAsset(asset, false);
|
|
||||||
}
|
|
||||||
return asset;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPreviousAsset = async (currentAsset: AssetResponseDto, preload: boolean = true) => {
|
const getNextAsset = async (currentAsset: AssetResponseDto) => {
|
||||||
const laterTimelineAsset = await timelineManager.getLaterAsset(currentAsset);
|
const earlierTimelineAsset = await timelineManager.getEarlierAsset(currentAsset);
|
||||||
if (laterTimelineAsset) {
|
if (!earlierTimelineAsset) {
|
||||||
const asset = await assetCacheManager.getAsset({ ...authManager.params, id: laterTimelineAsset.id });
|
return;
|
||||||
if (preload) {
|
|
||||||
// also pre-cache an extra one, to pre-cache these assetInfos for the next nav after this one is complete
|
|
||||||
void getPreviousAsset(asset, false);
|
|
||||||
}
|
|
||||||
return asset;
|
|
||||||
}
|
}
|
||||||
|
return getAsset(earlierTimelineAsset.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPreviousAsset = async (currentAsset: AssetResponseDto) => {
|
||||||
|
const laterTimelineAsset = await timelineManager.getLaterAsset(currentAsset);
|
||||||
|
if (!laterTimelineAsset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return getAsset(laterTimelineAsset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
let assetCursor = $state<AssetCursor>({
|
let assetCursor = $state<AssetCursor>({
|
||||||
|
|
@ -87,10 +88,12 @@
|
||||||
|
|
||||||
const handleRandom = async () => {
|
const handleRandom = async () => {
|
||||||
const randomAsset = await timelineManager.getRandomAsset();
|
const randomAsset = await timelineManager.getRandomAsset();
|
||||||
if (randomAsset) {
|
if (!randomAsset) {
|
||||||
await navigate({ targetRoute: 'current', assetId: randomAsset.id });
|
return;
|
||||||
return { id: randomAsset.id };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await navigate({ targetRoute: 'current', assetId: randomAsset.id });
|
||||||
|
return { id: randomAsset.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = async (asset: { id: string }) => {
|
const handleClose = async (asset: { id: string }) => {
|
||||||
|
|
@ -180,12 +183,14 @@
|
||||||
};
|
};
|
||||||
const handleUndoDelete = async (assets: TimelineAsset[]) => {
|
const handleUndoDelete = async (assets: TimelineAsset[]) => {
|
||||||
timelineManager.upsertAssets(assets);
|
timelineManager.upsertAssets(assets);
|
||||||
if (assets.length > 0) {
|
if (assets.length === 0) {
|
||||||
const restoredAsset = assets[0];
|
return;
|
||||||
const asset = await getAssetInfo({ ...authManager.params, id: restoredAsset.id });
|
|
||||||
assetViewingStore.setAsset(asset);
|
|
||||||
await navigate({ targetRoute: 'current', assetId: restoredAsset.id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restoredAsset = assets[0];
|
||||||
|
const asset = await getAssetInfo({ ...authManager.params, id: restoredAsset.id });
|
||||||
|
assetViewingStore.setAsset(asset);
|
||||||
|
await navigate({ targetRoute: 'current', assetId: restoredAsset.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateOrUpload = (asset: AssetResponseDto) => {
|
const handleUpdateOrUpload = (asset: AssetResponseDto) => {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,17 @@ export function getServerErrorMessage(error: unknown) {
|
||||||
return data?.message || error.message;
|
return data?.message || error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleError(error: unknown, message: string) {
|
export function standardizeError(error: unknown) {
|
||||||
if ((error as Error)?.name === 'AbortError') {
|
return error instanceof Error ? error : new Error(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleError(error: unknown, localizedMessage: string) {
|
||||||
|
const standardizedError = standardizeError(error);
|
||||||
|
if (standardizedError.name === 'AbortError') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(`[handleError]: ${message}`, error, (error as Error)?.stack);
|
console.error(`[handleError]: ${standardizedError}`, error, standardizedError.stack);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let serverMessage = getServerErrorMessage(error);
|
let serverMessage = getServerErrorMessage(error);
|
||||||
|
|
@ -32,13 +37,22 @@ export function handleError(error: unknown, message: string) {
|
||||||
serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
|
serverMessage = `${String(serverMessage).slice(0, 75)}\n(Immich Server Error)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = serverMessage || message;
|
const errorMessage = serverMessage || localizedMessage;
|
||||||
|
|
||||||
toastManager.danger(errorMessage);
|
toastManager.danger(errorMessage);
|
||||||
|
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return message;
|
return localizedMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleErrorAsync<T>(fn: () => Promise<T>, localizedMessage: string): Promise<T | undefined> {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
handleError(error, localizedMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the state of asynchronous invocations to handle race conditions and stale operations.
|
* Tracks the state of asynchronous invocations to handle race conditions and stale operations.
|
||||||
* This class helps manage concurrent operations by tracking which invocations are active
|
* This class helps manage concurrent operations by tracking which invocations are active
|
||||||
|
|
@ -51,10 +53,12 @@ export class InvocationTracker {
|
||||||
return this.invocationsStarted !== this.invocationsEnded;
|
return this.invocationsStarted !== this.invocationsEnded;
|
||||||
}
|
}
|
||||||
|
|
||||||
async invoke<T>(invocable: () => Promise<T>) {
|
async invoke<T>(invocable: () => Promise<T>, localizedMessage: string) {
|
||||||
const invocation = this.startInvocation();
|
const invocation = this.startInvocation();
|
||||||
try {
|
try {
|
||||||
return await invocable();
|
return await invocable();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
handleError(error, localizedMessage);
|
||||||
} finally {
|
} finally {
|
||||||
invocation.endInvocation();
|
invocation.endInvocation();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ export interface boundingBox {
|
||||||
export const getBoundingBox = (
|
export const getBoundingBox = (
|
||||||
faces: Faces[],
|
faces: Faces[],
|
||||||
zoom: ZoomImageWheelState,
|
zoom: ZoomImageWheelState,
|
||||||
photoViewer: HTMLImageElement | null,
|
photoViewer: HTMLImageElement | undefined,
|
||||||
): boundingBox[] => {
|
): boundingBox[] => {
|
||||||
const boxes: boundingBox[] = [];
|
const boxes: boundingBox[] = [];
|
||||||
|
|
||||||
if (photoViewer === null) {
|
if (!photoViewer) {
|
||||||
return boxes;
|
return boxes;
|
||||||
}
|
}
|
||||||
const clientHeight = photoViewer.clientHeight;
|
const clientHeight = photoViewer.clientHeight;
|
||||||
|
|
@ -93,7 +93,7 @@ export const zoomImageToBase64 = async (
|
||||||
|
|
||||||
image = img;
|
image = img;
|
||||||
}
|
}
|
||||||
if (image === null) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { boundingBoxX1: x1, boundingBoxX2: x2, boundingBoxY1: y1, boundingBoxY2: y2, imageWidth, imageHeight } = face;
|
const { boundingBoxX1: x1, boundingBoxX2: x2, boundingBoxY1: y1, boundingBoxY2: y2, imageWidth, imageHeight } = face;
|
||||||
|
|
@ -121,11 +121,9 @@ export const zoomImageToBase64 = async (
|
||||||
canvas.height = faceHeight;
|
canvas.height = faceHeight;
|
||||||
|
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
if (context) {
|
if (!context) {
|
||||||
context.drawImage(faceImage, coordinates.x1, coordinates.y1, faceWidth, faceHeight, 0, 0, faceWidth, faceHeight);
|
|
||||||
|
|
||||||
return canvas.toDataURL();
|
|
||||||
} else {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
context.drawImage(faceImage, coordinates.x1, coordinates.y1, faceWidth, faceHeight, 0, 0, faceWidth, faceHeight);
|
||||||
|
return canvas.toDataURL();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue