From f59417cc779652f993ddbd55b7b6ce90240edbf5 Mon Sep 17 00:00:00 2001
From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Date: Wed, 19 Nov 2025 15:49:15 +0100
Subject: [PATCH] chore(web): refactor replace asset (#23972)
---
.../asset-viewer/asset-viewer-nav-bar.svelte | 4 +-
.../asset-viewer/asset-viewer.svelte | 15 ++++++-
web/src/lib/managers/event-manager.svelte.ts | 2 +
web/src/lib/services/asset.service.ts | 11 +++++
web/src/lib/utils/file-uploader.ts | 45 ++++++-------------
5 files changed, 43 insertions(+), 34 deletions(-)
create mode 100644 web/src/lib/services/asset.service.ts
diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
index 7daade6379..0dad2793bf 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
@@ -25,12 +25,12 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
+ import { handleReplaceAsset } from '$lib/services/asset.service';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetJobName, getSharedLink } from '$lib/utils';
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
- import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import {
AssetJobName,
@@ -227,7 +227,7 @@
openFileUploadDialog({ multiple: false, assetId: asset.id })}
+ onClick={() => handleReplaceAsset(asset.id)}
text={$t('replace_with_upload')}
/>
{#if !asset.isArchived && !asset.isTrashed}
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index 8ff7a00710..e26c85ad07 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -1,11 +1,13 @@
+
+
{
+ const [newAssetId] = await openFileUploadDialog({ multiple: false });
+ await copyAsset({ assetCopyDto: { sourceId: oldAssetId, targetId: newAssetId } });
+ await deleteAssets({ assetBulkDeleteDto: { ids: [oldAssetId], force: true } });
+
+ eventManager.emit('AssetReplace', { oldAssetId, newAssetId });
+};
diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts
index 3f602bdb29..516d682625 100644
--- a/web/src/lib/utils/file-uploader.ts
+++ b/web/src/lib/utils/file-uploader.ts
@@ -12,7 +12,6 @@ import {
AssetMediaStatus,
AssetVisibility,
checkBulkUpload,
- getAssetOriginalPath,
getBaseUrl,
type AssetMediaResponseDto,
} from '@immich/sdk';
@@ -44,12 +43,10 @@ export const addDummyItems = () => {
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
-type FileUploadParam = { multiple?: boolean } & (
- | { albumId?: string; assetId?: never }
- | { albumId?: never; assetId?: string }
-);
+type FileUploadParam = { multiple?: boolean; albumId?: string };
+
export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
- const { albumId, multiple = true, assetId } = options;
+ const { albumId, multiple = true } = options;
const extensions = uploadManager.getExtensions();
return new Promise((resolve, reject) => {
@@ -68,7 +65,7 @@ export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
}
const files = Array.from(target.files);
- resolve(fileUploadHandler({ files, albumId, replaceAssetId: assetId }));
+ resolve(fileUploadHandler({ files, albumId }));
},
{ passive: true },
);
@@ -88,7 +85,6 @@ type FileUploadHandlerParams = Omit => {
const extensions = uploadManager.getExtensions();
@@ -99,9 +95,7 @@ export const fileUploadHandler = async ({
const deviceAssetId = getDeviceAssetId(file);
uploadAssetsStore.addItem({ id: deviceAssetId, file, albumId });
promises.push(
- uploadExecutionQueue.addTask(() =>
- fileUploader({ assetFile: file, deviceAssetId, albumId, replaceAssetId, isLockedAssets }),
- ),
+ uploadExecutionQueue.addTask(() => fileUploader({ assetFile: file, deviceAssetId, albumId, isLockedAssets })),
);
}
}
@@ -127,7 +121,6 @@ async function fileUploader({
assetFile,
deviceAssetId,
albumId,
- replaceAssetId,
isLockedAssets = false,
}: FileUploaderParams): Promise {
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
@@ -183,27 +176,17 @@ async function fileUploader({
const queryParams = asQueryString(authManager.params);
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_uploading') });
- if (replaceAssetId) {
- const response = await uploadRequest({
- url: getBaseUrl() + getAssetOriginalPath(replaceAssetId) + (queryParams ? `?${queryParams}` : ''),
- method: 'PUT',
- data: formData,
- onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
- });
- responseData = response.data;
- } else {
- const response = await uploadRequest({
- url: getBaseUrl() + '/assets' + (queryParams ? `?${queryParams}` : ''),
- data: formData,
- onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
- });
+ const response = await uploadRequest({
+ url: getBaseUrl() + '/assets' + (queryParams ? `?${queryParams}` : ''),
+ data: formData,
+ onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
+ });
- if (![200, 201].includes(response.status)) {
- throw new Error($t('errors.unable_to_upload_file'));
- }
-
- responseData = response.data;
+ if (![200, 201].includes(response.status)) {
+ throw new Error($t('errors.unable_to_upload_file'));
}
+
+ responseData = response.data;
}
if (responseData.status === AssetMediaStatus.Duplicate) {