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) {