refactor(web): workflow create action (#25369)

This commit is contained in:
Jason Rasmussen 2026-01-19 12:41:28 -05:00 committed by GitHub
parent 97a594556b
commit 2b77dc8e1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 42 deletions

View file

@ -73,6 +73,7 @@ export type Events = {
LibraryUpdate: [LibraryResponseDto]; LibraryUpdate: [LibraryResponseDto];
LibraryDelete: [{ id: string }]; LibraryDelete: [{ id: string }];
WorkflowCreate: [WorkflowResponseDto];
WorkflowUpdate: [WorkflowResponseDto]; WorkflowUpdate: [WorkflowResponseDto];
WorkflowDelete: [WorkflowResponseDto]; WorkflowDelete: [WorkflowResponseDto];

View file

@ -17,12 +17,13 @@ import {
type PluginFilterResponseDto, type PluginFilterResponseDto,
type PluginTriggerResponseDto, type PluginTriggerResponseDto,
type WorkflowActionItemDto, type WorkflowActionItemDto,
type WorkflowCreateDto,
type WorkflowFilterItemDto, type WorkflowFilterItemDto,
type WorkflowResponseDto, type WorkflowResponseDto,
type WorkflowUpdateDto, type WorkflowUpdateDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { mdiCodeJson, mdiDelete, mdiPause, mdiPencil, mdiPlay } from '@mdi/js'; import { mdiCodeJson, mdiDelete, mdiPause, mdiPencil, mdiPlay, mdiPlus } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n'; import type { MessageFormatter } from 'svelte-i18n';
export type PickerSubType = 'album-picker' | 'people-picker'; export type PickerSubType = 'album-picker' | 'people-picker';
@ -318,6 +319,23 @@ export const handleUpdateWorkflow = async (
return updateWorkflow({ id: workflowId, workflowUpdateDto: updateDto }); return updateWorkflow({ id: workflowId, workflowUpdateDto: updateDto });
}; };
export const getWorkflowsActions = ($t: MessageFormatter) => {
const Create: ActionItem = {
title: $t('create_workflow'),
icon: mdiPlus,
onAction: () =>
handleCreateWorkflow({
name: $t('untitled_workflow'),
triggerType: PluginTriggerType.AssetCreate,
filters: [],
actions: [],
enabled: false,
}),
};
return { Create };
};
export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowResponseDto) => { export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowResponseDto) => {
const ToggleEnabled: ActionItem = { const ToggleEnabled: ActionItem = {
title: workflow.enabled ? $t('disable') : $t('enable'), title: workflow.enabled ? $t('disable') : $t('enable'),
@ -356,22 +374,12 @@ export const getWorkflowShowSchemaAction = (
onAction: onToggle, onAction: onToggle,
}); });
export const handleCreateWorkflow = async (): Promise<WorkflowResponseDto | undefined> => { const handleCreateWorkflow = async (dto: WorkflowCreateDto) => {
const $t = await getFormatter(); const $t = await getFormatter();
try { try {
const workflow = await createWorkflow({ const response = await createWorkflow({ workflowCreateDto: dto });
workflowCreateDto: { eventManager.emit('WorkflowCreate', response);
name: $t('untitled_workflow'),
triggerType: PluginTriggerType.AssetCreate,
filters: [],
actions: [],
enabled: false,
},
});
await goto(Route.viewWorkflow(workflow));
return workflow;
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_create')); handleError(error, $t('errors.unable_to_create'));
} }

View file

@ -1,15 +1,17 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import emptyWorkflows from '$lib/assets/empty-workflows.svg'; import emptyWorkflows from '$lib/assets/empty-workflows.svg';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import OnEvents from '$lib/components/OnEvents.svelte'; import OnEvents from '$lib/components/OnEvents.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { Route } from '$lib/route';
import { import {
getWorkflowActions, getWorkflowActions,
getWorkflowsActions,
getWorkflowShowSchemaAction, getWorkflowShowSchemaAction,
handleCreateWorkflow,
type WorkflowPayload, type WorkflowPayload,
} from '$lib/services/workflow.service'; } from '$lib/services/workflow.service';
import type { PluginFilterResponseDto, WorkflowResponseDto } from '@immich/sdk'; import { type PluginFilterResponseDto, type WorkflowResponseDto } from '@immich/sdk';
import { import {
Button, Button,
Card, Card,
@ -18,15 +20,13 @@
CardHeader, CardHeader,
CardTitle, CardTitle,
CodeBlock, CodeBlock,
HStack,
Icon,
IconButton, IconButton,
MenuItemType, MenuItemType,
menuManager, menuManager,
Text, Text,
VStack, VStack,
} from '@immich/ui'; } from '@immich/ui';
import { mdiClose, mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiClose, mdiDotsVertical } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { SvelteMap, SvelteSet } from 'svelte/reactivity'; import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -40,7 +40,6 @@
let workflows = $state<WorkflowResponseDto[]>(data.workflows); let workflows = $state<WorkflowResponseDto[]>(data.workflows);
const expandedWorkflows = new SvelteSet<string>(); const expandedWorkflows = new SvelteSet<string>();
const pluginFilterLookup = new SvelteMap<string, PluginFilterResponseDto>(); const pluginFilterLookup = new SvelteMap<string, PluginFilterResponseDto>();
const pluginActionLookup = new SvelteMap<string, PluginFilterResponseDto>(); const pluginActionLookup = new SvelteMap<string, PluginFilterResponseDto>();
@ -90,16 +89,6 @@
const getJson = (workflow: WorkflowResponseDto) => JSON.stringify(constructPayload(workflow), null, 2); const getJson = (workflow: WorkflowResponseDto) => JSON.stringify(constructPayload(workflow), null, 2);
const onWorkflowUpdate = (updatedWorkflow: WorkflowResponseDto) => {
workflows = workflows.map((currentWorkflow) =>
currentWorkflow.id === updatedWorkflow.id ? updatedWorkflow : currentWorkflow,
);
};
const onWorkflowDelete = (deletedWorkflow: WorkflowResponseDto) => {
workflows = workflows.filter((currentWorkflow) => currentWorkflow.id !== deletedWorkflow.id);
};
const getFilterLabel = (filterId: string) => { const getFilterLabel = (filterId: string) => {
const meta = pluginFilterLookup.get(filterId); const meta = pluginFilterLookup.get(filterId);
return meta?.title ?? $t('filter'); return meta?.title ?? $t('filter');
@ -138,9 +127,23 @@
], ],
}); });
}; };
const { Create } = $derived(getWorkflowsActions($t));
const onWorkflowCreate = async (response: WorkflowResponseDto) => {
await goto(Route.viewWorkflow(response));
};
const onWorkflowUpdate = (response: WorkflowResponseDto) => {
workflows = workflows.map((workflow) => (workflow.id === response.id ? response : workflow));
};
const onWorkflowDelete = (response: WorkflowResponseDto) => {
workflows = workflows.filter(({ id }) => id !== response.id);
};
</script> </script>
<OnEvents {onWorkflowUpdate} {onWorkflowDelete} /> <OnEvents {onWorkflowCreate} {onWorkflowUpdate} {onWorkflowDelete} />
{#snippet chipItem(title: string)} {#snippet chipItem(title: string)}
<span class="rounded-xl border border-gray-200/80 px-3 py-1.5 text-sm dark:border-gray-600 bg-light"> <span class="rounded-xl border border-gray-200/80 px-3 py-1.5 text-sm dark:border-gray-600 bg-light">
@ -148,23 +151,14 @@
</span> </span>
{/snippet} {/snippet}
<UserPageLayout title={data.meta.title} scrollbar={false}> <UserPageLayout title={data.meta.title} actions={[Create]} scrollbar={false}>
{#snippet buttons()}
<HStack gap={1}>
<Button size="small" variant="ghost" color="secondary" onclick={handleCreateWorkflow}>
<Icon icon={mdiPlus} size="18" />
{$t('create_workflow')}
</Button>
</HStack>
{/snippet}
<section class="flex place-content-center sm:mx-4"> <section class="flex place-content-center sm:mx-4">
<section class="w-full pb-28 sm:w-5/6 md:w-4xl"> <section class="w-full pb-28 sm:w-5/6 md:w-4xl">
{#if workflows.length === 0} {#if workflows.length === 0}
<EmptyPlaceholder <EmptyPlaceholder
title={$t('create_first_workflow')} title={$t('create_first_workflow')}
text={$t('workflows_help_text')} text={$t('workflows_help_text')}
onClick={handleCreateWorkflow} onClick={() => Create.onAction(Create)}
src={emptyWorkflows} src={emptyWorkflows}
class="mt-10 mx-auto" class="mt-10 mx-auto"
/> />