mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(web,server): run jobs for specific assets (#3712)
* feat(web,server): manually queue asset job * chore: open api * chore: tests
This commit is contained in:
parent
66490d5db4
commit
5e901e4d21
26 changed files with 896 additions and 18 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { clickOutside } from '$lib/utils/click-outside';
|
||||
import type { AssetResponseDto } from '@api';
|
||||
import { AssetJobName, AssetResponseDto, AssetTypeEnum, api } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
||||
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
|
||||
|
|
@ -29,7 +29,22 @@
|
|||
|
||||
const isOwner = asset.ownerId === $page.data.user?.id;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
type MenuItemEvent = 'addToAlbum' | 'addToSharedAlbum' | 'asProfileImage' | 'runJob';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
goBack: void;
|
||||
stopMotionPhoto: void;
|
||||
playMotionPhoto: void;
|
||||
download: void;
|
||||
showDetail: void;
|
||||
favorite: void;
|
||||
delete: void;
|
||||
toggleArchive: void;
|
||||
addToAlbum: void;
|
||||
addToSharedAlbum: void;
|
||||
asProfileImage: void;
|
||||
runJob: AssetJobName;
|
||||
}>();
|
||||
|
||||
let contextMenuPosition = { x: 0, y: 0 };
|
||||
let isShowAssetOptions = false;
|
||||
|
|
@ -39,7 +54,12 @@
|
|||
isShowAssetOptions = !isShowAssetOptions;
|
||||
};
|
||||
|
||||
const onMenuClick = (eventName: string) => {
|
||||
const onJobClick = (name: AssetJobName) => {
|
||||
isShowAssetOptions = false;
|
||||
dispatch('runJob', name);
|
||||
};
|
||||
|
||||
const onMenuClick = (eventName: MenuItemEvent) => {
|
||||
isShowAssetOptions = false;
|
||||
dispatch(eventName);
|
||||
};
|
||||
|
|
@ -114,22 +134,35 @@
|
|||
{#if isOwner}
|
||||
<CircleIconButton isOpacity={true} logo={DeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
||||
<div use:clickOutside on:outclick={() => (isShowAssetOptions = false)}>
|
||||
<CircleIconButton isOpacity={true} logo={DotsVertical} on:click={showOptionsMenu} title="More">
|
||||
{#if isShowAssetOptions}
|
||||
<ContextMenu {...contextMenuPosition} direction="left">
|
||||
<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
|
||||
<MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" />
|
||||
<CircleIconButton isOpacity={true} logo={DotsVertical} on:click={showOptionsMenu} title="More" />
|
||||
{#if isShowAssetOptions}
|
||||
<ContextMenu {...contextMenuPosition} direction="left">
|
||||
<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
|
||||
<MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" />
|
||||
|
||||
{#if isOwner}
|
||||
{#if isOwner}
|
||||
<MenuOption
|
||||
on:click={() => dispatch('toggleArchive')}
|
||||
text={asset.isArchived ? 'Unarchive' : 'Archive'}
|
||||
/>
|
||||
<MenuOption on:click={() => onMenuClick('asProfileImage')} text="As profile picture" />
|
||||
<MenuOption
|
||||
on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
|
||||
text={api.getAssetJobName(AssetJobName.RefreshMetadata)}
|
||||
/>
|
||||
<MenuOption
|
||||
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
|
||||
text={api.getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
||||
/>
|
||||
{#if asset.type === AssetTypeEnum.Video}
|
||||
<MenuOption
|
||||
on:click={() => dispatch('toggleArchive')}
|
||||
text={asset.isArchived ? 'Unarchive' : 'Archive'}
|
||||
on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
|
||||
text={api.getAssetJobName(AssetJobName.TranscodeVideo)}
|
||||
/>
|
||||
{/if}
|
||||
<MenuOption on:click={() => onMenuClick('asProfileImage')} text="As profile picture" />
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</CircleIconButton>
|
||||
{/if}
|
||||
</ContextMenu>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { AlbumResponseDto, api, AssetResponseDto, AssetTypeEnum, SharedLinkResponseDto } from '@api';
|
||||
import { AlbumResponseDto, api, AssetJobName, AssetResponseDto, AssetTypeEnum, SharedLinkResponseDto } from '@api';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
|
||||
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
|
||||
|
|
@ -245,6 +245,15 @@
|
|||
return 'Asset';
|
||||
}
|
||||
};
|
||||
|
||||
const handleRunJob = async (name: AssetJobName) => {
|
||||
try {
|
||||
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
|
||||
notificationController.show({ type: NotificationType.Info, message: api.getAssetJobMessage(name) });
|
||||
} catch (error) {
|
||||
handleError(error, `Unable to submit job`);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<section
|
||||
|
|
@ -270,6 +279,7 @@
|
|||
on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
|
||||
on:toggleArchive={toggleArchive}
|
||||
on:asProfileImage={() => (isShowProfileImageCrop = true)}
|
||||
on:runJob={({ detail: job }) => handleRunJob(job)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<script lang="ts">
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import {
|
||||
NotificationType,
|
||||
notificationController,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { AssetJobName, AssetTypeEnum, api } from '@api';
|
||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||
|
||||
export let jobs: AssetJobName[] = [
|
||||
AssetJobName.RegenerateThumbnail,
|
||||
AssetJobName.RefreshMetadata,
|
||||
AssetJobName.TranscodeVideo,
|
||||
];
|
||||
|
||||
const { getAssets, clearSelect } = getAssetControlContext();
|
||||
|
||||
$: isAllVideos = Array.from(getAssets()).every((asset) => asset.type === AssetTypeEnum.Video);
|
||||
|
||||
const handleRunJob = async (name: AssetJobName) => {
|
||||
try {
|
||||
const ids = Array.from(getAssets()).map(({ id }) => id);
|
||||
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
|
||||
notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info });
|
||||
clearSelect();
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to submit job');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#each jobs as job}
|
||||
{#if isAllVideos || job !== AssetJobName.TranscodeVideo}
|
||||
<MenuOption text={api.getAssetJobName(job)} on:click={() => handleRunJob(job)} />
|
||||
{/if}
|
||||
{/each}
|
||||
Loading…
Add table
Add a link
Reference in a new issue