mirror of
https://github.com/immich-app/immich.git
synced 2025-11-25 15:55:17 -05:00
chore(web): refactor replace asset (#23972)
This commit is contained in:
parent
11cec56e80
commit
f59417cc77
@ -25,12 +25,12 @@
|
|||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
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 { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
import { getAssetJobName, getSharedLink } from '$lib/utils';
|
import { getAssetJobName, getSharedLink } from '$lib/utils';
|
||||||
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
|
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import {
|
import {
|
||||||
AssetJobName,
|
AssetJobName,
|
||||||
@ -227,7 +227,7 @@
|
|||||||
<ArchiveAction {asset} {onAction} {preAction} />
|
<ArchiveAction {asset} {onAction} {preAction} />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
icon={mdiUpload}
|
icon={mdiUpload}
|
||||||
onClick={() => openFileUploadDialog({ multiple: false, assetId: asset.id })}
|
onClick={() => handleReplaceAsset(asset.id)}
|
||||||
text={$t('replace_with_upload')}
|
text={$t('replace_with_upload')}
|
||||||
/>
|
/>
|
||||||
{#if !asset.isArchived && !asset.isTrashed}
|
{#if !asset.isArchived && !asset.isTrashed}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
import { focusTrap } from '$lib/actions/focus-trap';
|
import { focusTrap } from '$lib/actions/focus-trap';
|
||||||
import type { Action, OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
|
import type { Action, OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
|
||||||
import MotionPhotoAction from '$lib/components/asset-viewer/actions/motion-photo-action.svelte';
|
import MotionPhotoAction from '$lib/components/asset-viewer/actions/motion-photo-action.svelte';
|
||||||
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
|
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
|
||||||
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
||||||
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
||||||
import { AssetAction, ProjectionType } from '$lib/constants';
|
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||||
|
import { AppRoute, AssetAction, ProjectionType } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
@ -363,6 +365,15 @@
|
|||||||
selectedEditType = type;
|
selectedEditType = type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => {
|
||||||
|
if (oldAssetId !== asset.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((promise) => setTimeout(promise, 500));
|
||||||
|
await goto(`${AppRoute.PHOTOS}/${newAssetId}`);
|
||||||
|
};
|
||||||
|
|
||||||
let isFullScreen = $derived(fullscreenElement !== null);
|
let isFullScreen = $derived(fullscreenElement !== null);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -388,6 +399,8 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<OnEvents onAssetReplace={handleAssetReplace} />
|
||||||
|
|
||||||
<svelte:document bind:fullscreenElement />
|
<svelte:document bind:fullscreenElement />
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|||||||
@ -16,6 +16,8 @@ export type Events = {
|
|||||||
LanguageChange: [{ name: string; code: string; rtl?: boolean }];
|
LanguageChange: [{ name: string; code: string; rtl?: boolean }];
|
||||||
ThemeChange: [ThemeSetting];
|
ThemeChange: [ThemeSetting];
|
||||||
|
|
||||||
|
AssetReplace: [{ oldAssetId: string; newAssetId: string }];
|
||||||
|
|
||||||
AlbumDelete: [AlbumResponseDto];
|
AlbumDelete: [AlbumResponseDto];
|
||||||
|
|
||||||
SharedLinkCreate: [SharedLinkResponseDto];
|
SharedLinkCreate: [SharedLinkResponseDto];
|
||||||
|
|||||||
11
web/src/lib/services/asset.service.ts
Normal file
11
web/src/lib/services/asset.service.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||||
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
|
import { copyAsset, deleteAssets } from '@immich/sdk';
|
||||||
|
|
||||||
|
export const handleReplaceAsset = async (oldAssetId: string) => {
|
||||||
|
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 });
|
||||||
|
};
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
AssetMediaStatus,
|
AssetMediaStatus,
|
||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
checkBulkUpload,
|
checkBulkUpload,
|
||||||
getAssetOriginalPath,
|
|
||||||
getBaseUrl,
|
getBaseUrl,
|
||||||
type AssetMediaResponseDto,
|
type AssetMediaResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
@ -44,12 +43,10 @@ export const addDummyItems = () => {
|
|||||||
|
|
||||||
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
|
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
|
||||||
|
|
||||||
type FileUploadParam = { multiple?: boolean } & (
|
type FileUploadParam = { multiple?: boolean; albumId?: string };
|
||||||
| { albumId?: string; assetId?: never }
|
|
||||||
| { albumId?: never; assetId?: string }
|
|
||||||
);
|
|
||||||
export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
|
export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
|
||||||
const { albumId, multiple = true, assetId } = options;
|
const { albumId, multiple = true } = options;
|
||||||
const extensions = uploadManager.getExtensions();
|
const extensions = uploadManager.getExtensions();
|
||||||
|
|
||||||
return new Promise<string[]>((resolve, reject) => {
|
return new Promise<string[]>((resolve, reject) => {
|
||||||
@ -68,7 +65,7 @@ export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
|
|||||||
}
|
}
|
||||||
const files = Array.from(target.files);
|
const files = Array.from(target.files);
|
||||||
|
|
||||||
resolve(fileUploadHandler({ files, albumId, replaceAssetId: assetId }));
|
resolve(fileUploadHandler({ files, albumId }));
|
||||||
},
|
},
|
||||||
{ passive: true },
|
{ passive: true },
|
||||||
);
|
);
|
||||||
@ -88,7 +85,6 @@ type FileUploadHandlerParams = Omit<FileUploaderParams, 'deviceAssetId' | 'asset
|
|||||||
export const fileUploadHandler = async ({
|
export const fileUploadHandler = async ({
|
||||||
files,
|
files,
|
||||||
albumId,
|
albumId,
|
||||||
replaceAssetId,
|
|
||||||
isLockedAssets = false,
|
isLockedAssets = false,
|
||||||
}: FileUploadHandlerParams): Promise<string[]> => {
|
}: FileUploadHandlerParams): Promise<string[]> => {
|
||||||
const extensions = uploadManager.getExtensions();
|
const extensions = uploadManager.getExtensions();
|
||||||
@ -99,9 +95,7 @@ export const fileUploadHandler = async ({
|
|||||||
const deviceAssetId = getDeviceAssetId(file);
|
const deviceAssetId = getDeviceAssetId(file);
|
||||||
uploadAssetsStore.addItem({ id: deviceAssetId, file, albumId });
|
uploadAssetsStore.addItem({ id: deviceAssetId, file, albumId });
|
||||||
promises.push(
|
promises.push(
|
||||||
uploadExecutionQueue.addTask(() =>
|
uploadExecutionQueue.addTask(() => fileUploader({ assetFile: file, deviceAssetId, albumId, isLockedAssets })),
|
||||||
fileUploader({ assetFile: file, deviceAssetId, albumId, replaceAssetId, isLockedAssets }),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +121,6 @@ async function fileUploader({
|
|||||||
assetFile,
|
assetFile,
|
||||||
deviceAssetId,
|
deviceAssetId,
|
||||||
albumId,
|
albumId,
|
||||||
replaceAssetId,
|
|
||||||
isLockedAssets = false,
|
isLockedAssets = false,
|
||||||
}: FileUploaderParams): Promise<string | undefined> {
|
}: FileUploaderParams): Promise<string | undefined> {
|
||||||
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
|
const fileCreatedAt = new Date(assetFile.lastModified).toISOString();
|
||||||
@ -183,27 +176,17 @@ async function fileUploader({
|
|||||||
const queryParams = asQueryString(authManager.params);
|
const queryParams = asQueryString(authManager.params);
|
||||||
|
|
||||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_uploading') });
|
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_uploading') });
|
||||||
if (replaceAssetId) {
|
const response = await uploadRequest<AssetMediaResponseDto>({
|
||||||
const response = await uploadRequest<AssetMediaResponseDto>({
|
url: getBaseUrl() + '/assets' + (queryParams ? `?${queryParams}` : ''),
|
||||||
url: getBaseUrl() + getAssetOriginalPath(replaceAssetId) + (queryParams ? `?${queryParams}` : ''),
|
data: formData,
|
||||||
method: 'PUT',
|
onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
|
||||||
data: formData,
|
});
|
||||||
onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
|
|
||||||
});
|
|
||||||
responseData = response.data;
|
|
||||||
} else {
|
|
||||||
const response = await uploadRequest<AssetMediaResponseDto>({
|
|
||||||
url: getBaseUrl() + '/assets' + (queryParams ? `?${queryParams}` : ''),
|
|
||||||
data: formData,
|
|
||||||
onUploadProgress: (event) => uploadAssetsStore.updateProgress(deviceAssetId, event.loaded, event.total),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (![200, 201].includes(response.status)) {
|
if (![200, 201].includes(response.status)) {
|
||||||
throw new Error($t('errors.unable_to_upload_file'));
|
throw new Error($t('errors.unable_to_upload_file'));
|
||||||
}
|
|
||||||
|
|
||||||
responseData = response.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responseData = response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseData.status === AssetMediaStatus.Duplicate) {
|
if (responseData.status === AssetMediaStatus.Duplicate) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user