mirror of
https://github.com/immich-app/immich.git
synced 2026-03-26 20:00:44 +03:00
286 lines
8.9 KiB
TypeScript
286 lines
8.9 KiB
TypeScript
import { ProjectionType } from '$lib/constants';
|
|
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
|
import { eventManager } from '$lib/managers/event-manager.svelte';
|
|
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
|
import { user as authUser, preferences } from '$lib/stores/user.store';
|
|
import { getAssetJobName, getSharedLink, sleep } from '$lib/utils';
|
|
import { downloadUrl } from '$lib/utils/asset-utils';
|
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
|
import { handleError } from '$lib/utils/handle-error';
|
|
import { getFormatter } from '$lib/utils/i18n';
|
|
import { asQueryString } from '$lib/utils/shared-links';
|
|
import {
|
|
AssetJobName,
|
|
AssetTypeEnum,
|
|
AssetVisibility,
|
|
copyAsset,
|
|
deleteAssets,
|
|
getAssetInfo,
|
|
getBaseUrl,
|
|
runAssetJobs,
|
|
updateAsset,
|
|
type AssetJobsDto,
|
|
type AssetResponseDto,
|
|
} from '@immich/sdk';
|
|
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
|
|
import {
|
|
mdiAlertOutline,
|
|
mdiCogRefreshOutline,
|
|
mdiDatabaseRefreshOutline,
|
|
mdiDownload,
|
|
mdiDownloadBox,
|
|
mdiHeadSyncOutline,
|
|
mdiHeart,
|
|
mdiHeartOutline,
|
|
mdiImageRefreshOutline,
|
|
mdiInformationOutline,
|
|
mdiMotionPauseOutline,
|
|
mdiMotionPlayOutline,
|
|
mdiShareVariantOutline,
|
|
mdiTune,
|
|
} from '@mdi/js';
|
|
import type { MessageFormatter } from 'svelte-i18n';
|
|
import { get } from 'svelte/store';
|
|
|
|
export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) => {
|
|
const sharedLink = getSharedLink();
|
|
const currentAuthUser = get(authUser);
|
|
const isOwner = !!(currentAuthUser && currentAuthUser.id === asset.ownerId);
|
|
|
|
const Share: ActionItem = {
|
|
title: $t('share'),
|
|
icon: mdiShareVariantOutline,
|
|
type: $t('assets'),
|
|
$if: () => !!(get(authUser) && !asset.isTrashed && asset.visibility !== AssetVisibility.Locked),
|
|
onAction: () => modalManager.show(SharedLinkCreateModal, { assetIds: [asset.id] }),
|
|
};
|
|
|
|
const Download: ActionItem = {
|
|
title: $t('download'),
|
|
icon: mdiDownload,
|
|
shortcuts: { key: 'd', shift: true },
|
|
type: $t('assets'),
|
|
$if: () => !!currentAuthUser,
|
|
onAction: () => handleDownloadAsset(asset, { edited: true }),
|
|
};
|
|
|
|
const DownloadOriginal: ActionItem = {
|
|
title: $t('download_original'),
|
|
icon: mdiDownloadBox,
|
|
type: $t('assets'),
|
|
$if: () => !!currentAuthUser && asset.isEdited,
|
|
onAction: () => handleDownloadAsset(asset, { edited: false }),
|
|
};
|
|
|
|
const SharedLinkDownload: ActionItem = {
|
|
...Download,
|
|
$if: () => !currentAuthUser && sharedLink && sharedLink.allowDownload,
|
|
};
|
|
|
|
const PlayMotionPhoto: ActionItem = {
|
|
title: $t('play_motion_photo'),
|
|
icon: mdiMotionPlayOutline,
|
|
type: $t('assets'),
|
|
$if: () => !!asset.livePhotoVideoId && !assetViewerManager.isPlayingMotionPhoto,
|
|
onAction: () => {
|
|
assetViewerManager.isPlayingMotionPhoto = true;
|
|
},
|
|
};
|
|
|
|
const StopMotionPhoto: ActionItem = {
|
|
title: $t('stop_motion_photo'),
|
|
icon: mdiMotionPauseOutline,
|
|
type: $t('assets'),
|
|
$if: () => !!asset.livePhotoVideoId && assetViewerManager.isPlayingMotionPhoto,
|
|
onAction: () => {
|
|
assetViewerManager.isPlayingMotionPhoto = false;
|
|
},
|
|
};
|
|
|
|
const Favorite: ActionItem = {
|
|
title: $t('to_favorite'),
|
|
icon: mdiHeartOutline,
|
|
type: $t('assets'),
|
|
$if: () => isOwner && !asset.isFavorite,
|
|
onAction: () => handleFavorite(asset),
|
|
shortcuts: [{ key: 'f' }],
|
|
};
|
|
|
|
const Unfavorite: ActionItem = {
|
|
title: $t('unfavorite'),
|
|
icon: mdiHeart,
|
|
type: $t('assets'),
|
|
$if: () => isOwner && asset.isFavorite,
|
|
onAction: () => handleUnfavorite(asset),
|
|
shortcuts: [{ key: 'f' }],
|
|
};
|
|
|
|
const Offline: ActionItem = {
|
|
title: $t('asset_offline'),
|
|
icon: mdiAlertOutline,
|
|
type: $t('assets'),
|
|
color: 'danger',
|
|
$if: () => !!asset.isOffline,
|
|
onAction: () => assetViewerManager.toggleDetailPanel(),
|
|
};
|
|
|
|
const Info: ActionItem = {
|
|
title: $t('info'),
|
|
icon: mdiInformationOutline,
|
|
type: $t('assets'),
|
|
$if: () => asset.hasMetadata,
|
|
onAction: () => assetViewerManager.toggleDetailPanel(),
|
|
shortcuts: [{ key: 'i' }],
|
|
};
|
|
|
|
const Edit: ActionItem = {
|
|
title: $t('editor'),
|
|
icon: mdiTune,
|
|
$if: () =>
|
|
!sharedLink &&
|
|
isOwner &&
|
|
asset.type === AssetTypeEnum.Image &&
|
|
!asset.livePhotoVideoId &&
|
|
asset.exifInfo?.projectionType !== ProjectionType.EQUIRECTANGULAR &&
|
|
!asset.originalPath.toLowerCase().endsWith('.insp') &&
|
|
!asset.originalPath.toLowerCase().endsWith('.gif') &&
|
|
!asset.originalPath.toLowerCase().endsWith('.svg'),
|
|
onAction: () => assetViewerManager.openEditor(),
|
|
};
|
|
|
|
const RefreshFacesJob: ActionItem = {
|
|
title: getAssetJobName($t, AssetJobName.RefreshFaces),
|
|
icon: mdiHeadSyncOutline,
|
|
onAction: () => handleRunAssetJob({ name: AssetJobName.RefreshFaces, assetIds: [asset.id] }),
|
|
};
|
|
|
|
const RefreshMetadataJob: ActionItem = {
|
|
title: getAssetJobName($t, AssetJobName.RefreshMetadata),
|
|
icon: mdiDatabaseRefreshOutline,
|
|
onAction: () => handleRunAssetJob({ name: AssetJobName.RefreshMetadata, assetIds: [asset.id] }),
|
|
};
|
|
|
|
const RegenerateThumbnailJob: ActionItem = {
|
|
title: getAssetJobName($t, AssetJobName.RegenerateThumbnail),
|
|
icon: mdiImageRefreshOutline,
|
|
onAction: () => handleRunAssetJob({ name: AssetJobName.RegenerateThumbnail, assetIds: [asset.id] }),
|
|
};
|
|
|
|
const TranscodeVideoJob: ActionItem = {
|
|
title: getAssetJobName($t, AssetJobName.TranscodeVideo),
|
|
icon: mdiCogRefreshOutline,
|
|
onAction: () => handleRunAssetJob({ name: AssetJobName.TranscodeVideo, assetIds: [asset.id] }),
|
|
$if: () => asset.type === AssetTypeEnum.Video,
|
|
};
|
|
|
|
return {
|
|
Share,
|
|
Download,
|
|
DownloadOriginal,
|
|
SharedLinkDownload,
|
|
Offline,
|
|
Info,
|
|
Favorite,
|
|
Unfavorite,
|
|
PlayMotionPhoto,
|
|
StopMotionPhoto,
|
|
Edit,
|
|
RefreshFacesJob,
|
|
RefreshMetadataJob,
|
|
RegenerateThumbnailJob,
|
|
TranscodeVideoJob,
|
|
};
|
|
};
|
|
|
|
export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: { edited: boolean }) => {
|
|
const $t = await getFormatter();
|
|
|
|
const assets = [
|
|
{
|
|
filename: asset.originalFileName,
|
|
id: asset.id,
|
|
size: asset.exifInfo?.fileSizeInByte || 0,
|
|
},
|
|
];
|
|
|
|
const isAndroidMotionVideo = (asset: AssetResponseDto) => {
|
|
return asset.originalPath.includes('encoded-video');
|
|
};
|
|
|
|
if (asset.livePhotoVideoId) {
|
|
const motionAsset = await getAssetInfo({ ...authManager.params, id: asset.livePhotoVideoId });
|
|
if (!isAndroidMotionVideo(motionAsset) || get(preferences)?.download.includeEmbeddedVideos) {
|
|
assets.push({
|
|
filename: motionAsset.originalFileName,
|
|
id: asset.livePhotoVideoId,
|
|
size: motionAsset.exifInfo?.fileSizeInByte || 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
const queryParams = asQueryString(authManager.params);
|
|
|
|
for (const [i, { filename, id }] of assets.entries()) {
|
|
if (i !== 0) {
|
|
// play nice with Safari
|
|
await sleep(500);
|
|
}
|
|
|
|
try {
|
|
toastManager.success($t('downloading_asset_filename', { values: { filename: asset.originalFileName } }));
|
|
downloadUrl(
|
|
getBaseUrl() +
|
|
`/assets/${id}/original` +
|
|
(queryParams ? `?${queryParams}&edited=${edited}` : `?edited=${edited}`),
|
|
filename,
|
|
);
|
|
} catch (error) {
|
|
handleError(error, $t('errors.error_downloading', { values: { filename } }));
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleFavorite = async (asset: AssetResponseDto) => {
|
|
const $t = await getFormatter();
|
|
|
|
try {
|
|
const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: true } });
|
|
toastManager.success($t('added_to_favorites'));
|
|
eventManager.emit('AssetUpdate', response);
|
|
} catch (error) {
|
|
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } }));
|
|
}
|
|
};
|
|
|
|
const handleUnfavorite = async (asset: AssetResponseDto) => {
|
|
const $t = await getFormatter();
|
|
|
|
try {
|
|
const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: false } });
|
|
toastManager.success($t('removed_from_favorites'));
|
|
eventManager.emit('AssetUpdate', response);
|
|
} catch (error) {
|
|
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } }));
|
|
}
|
|
};
|
|
|
|
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 });
|
|
};
|
|
|
|
const handleRunAssetJob = async (dto: AssetJobsDto) => {
|
|
const $t = await getFormatter();
|
|
|
|
try {
|
|
await runAssetJobs({ assetJobsDto: dto });
|
|
toastManager.success(getAssetJobName($t, dto.name));
|
|
} catch (error) {
|
|
handleError(error, $t('errors.unable_to_submit_job'));
|
|
}
|
|
};
|