refactor ActionItem

This commit is contained in:
Alex Tran
2025-12-05 16:37:10 +00:00
parent 63e38f347e
commit 1e238e7a48
2 changed files with 152 additions and 89 deletions

View File

@@ -1,6 +1,12 @@
import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
import {
createWorkflow,
deleteWorkflow,
PluginTriggerType,
updateWorkflow as updateWorkflowApi,
updateWorkflow,
type PluginActionResponseDto,
type PluginContextType,
type PluginFilterResponseDto,
@@ -10,6 +16,9 @@ import {
type WorkflowResponseDto,
type WorkflowUpdateDto,
} from '@immich/sdk';
import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { mdiCodeJson, mdiDelete, mdiPause, mdiPencil, mdiPlay } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n';
export interface WorkflowPayload {
name: string;
@@ -295,5 +304,108 @@ export const handleUpdateWorkflow = async (
triggerType,
};
return updateWorkflowApi({ id: workflowId, workflowUpdateDto: updateDto });
return updateWorkflow({ id: workflowId, workflowUpdateDto: updateDto });
};
export const getWorkflowActions = ($t: MessageFormatter, workflow: WorkflowResponseDto) => {
const ToggleEnabled: ActionItem = {
title: workflow.enabled ? $t('disable') : $t('enable'),
icon: workflow.enabled ? mdiPause : mdiPlay,
color: workflow.enabled ? 'danger' : 'primary',
onAction: async () => {
await handleToggleWorkflowEnabled(workflow);
},
};
const Edit: ActionItem = {
title: $t('edit'),
icon: mdiPencil,
onAction: () => handleNavigateToWorkflow(workflow),
};
const Delete: ActionItem = {
title: $t('delete'),
icon: mdiDelete,
color: 'danger',
onAction: async () => {
await handleDeleteWorkflow(workflow);
},
};
return { ToggleEnabled, Edit, Delete };
};
export const getWorkflowShowSchemaAction = (
$t: MessageFormatter,
isExpanded: boolean,
onToggle: () => void,
): ActionItem => ({
title: isExpanded ? $t('hide_schema') : $t('show_schema'),
icon: mdiCodeJson,
onAction: onToggle,
});
export const handleCreateWorkflow = async (): Promise<WorkflowResponseDto | undefined> => {
const $t = await getFormatter();
try {
const workflow = await createWorkflow({
workflowCreateDto: {
name: $t('untitled_workflow'),
triggerType: PluginTriggerType.AssetCreate,
filters: [],
actions: [],
enabled: false,
},
});
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
return workflow;
} catch (error) {
handleError(error, $t('errors.unable_to_create'));
}
};
export const handleToggleWorkflowEnabled = async (
workflow: WorkflowResponseDto,
): Promise<WorkflowResponseDto | undefined> => {
const $t = await getFormatter();
try {
const updated = await updateWorkflow({
id: workflow.id,
workflowUpdateDto: { enabled: !workflow.enabled },
});
toastManager.success($t('workflow_updated'));
return updated;
} catch (error) {
handleError(error, $t('errors.unable_to_update_workflow'));
}
};
export const handleDeleteWorkflow = async (workflow: WorkflowResponseDto): Promise<boolean> => {
const $t = await getFormatter();
const confirmed = await modalManager.showDialog({
prompt: $t('workflow_delete_prompt'),
confirmColor: 'danger',
});
if (!confirmed) {
return false;
}
try {
await deleteWorkflow({ id: workflow.id });
toastManager.success($t('workflow_deleted'));
return true;
} catch (error) {
handleError(error, $t('errors.unable_to_delete_workflow'));
return false;
}
};
export const handleNavigateToWorkflow = async (workflow: WorkflowResponseDto): Promise<void> => {
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
};

View File

@@ -1,19 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import emptyWorkflows from '$lib/assets/empty-workflows.svg';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AppRoute } from '$lib/constants';
import type { WorkflowPayload } from '$lib/services/workflow.service';
import { handleError } from '$lib/utils/handle-error';
import {
createWorkflow,
deleteWorkflow,
PluginTriggerType,
updateWorkflow,
type PluginFilterResponseDto,
type WorkflowResponseDto,
} from '@immich/sdk';
getWorkflowActions,
getWorkflowShowSchemaAction,
handleCreateWorkflow,
handleDeleteWorkflow,
handleToggleWorkflowEnabled,
type WorkflowPayload,
} from '$lib/services/workflow.service';
import type { PluginFilterResponseDto, WorkflowResponseDto } from '@immich/sdk';
import {
Button,
Card,
@@ -27,12 +24,10 @@
IconButton,
MenuItemType,
menuManager,
modalManager,
Text,
toastManager,
VStack,
} from '@immich/ui';
import { mdiClose, mdiCodeJson, mdiDelete, mdiDotsVertical, mdiPause, mdiPencil, mdiPlay, mdiPlus } from '@mdi/js';
import { mdiClose, mdiDotsVertical, mdiPlus } from '@mdi/js';
import { t } from 'svelte-i18n';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import type { PageData } from './$types';
@@ -44,6 +39,7 @@
let { data }: Props = $props();
let workflows = $state<WorkflowResponseDto[]>(data.workflows);
const expandedWorkflows = new SvelteSet<string>();
const pluginFilterLookup = new SvelteMap<string, PluginFilterResponseDto>();
@@ -95,56 +91,20 @@
const getJson = (workflow: WorkflowResponseDto) => JSON.stringify(constructPayload(workflow), null, 2);
const handleToggleEnabled = async (workflow: WorkflowResponseDto) => {
try {
const updated = await updateWorkflow({
id: workflow.id,
workflowUpdateDto: { enabled: !workflow.enabled },
});
const onToggleEnabled = async (workflow: WorkflowResponseDto) => {
const updated = await handleToggleWorkflowEnabled(workflow);
if (updated) {
workflows = workflows.map((w) => (w.id === updated.id ? updated : w));
toastManager.success($t('workflow_updated'));
} catch (error) {
handleError(error, $t('errors.unable_to_update_workflow'));
}
};
const handleDeleteWorkflow = async (workflow: WorkflowResponseDto) => {
try {
const confirmed = await modalManager.showDialog({
prompt: $t('workflow_delete_prompt'),
confirmColor: 'danger',
});
if (!confirmed) {
return;
}
await deleteWorkflow({ id: workflow.id });
const onDeleteWorkflow = async (workflow: WorkflowResponseDto) => {
const deleted = await handleDeleteWorkflow(workflow);
if (deleted) {
workflows = workflows.filter((w) => w.id !== workflow.id);
toastManager.success($t('workflow_deleted'));
} catch (error) {
handleError(error, $t('errors.unable_to_delete_workflow'));
}
};
const handleEditWorkflow = async (workflow: WorkflowResponseDto) => {
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
};
const handleCreateWorkflow = async () => {
const workflow = await createWorkflow({
workflowCreateDto: {
name: 'New workflow',
triggerType: PluginTriggerType.AssetCreate,
filters: [],
actions: [],
enabled: false,
},
});
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
};
const getFilterLabel = (filterId: string) => {
const meta = pluginFilterLookup.get(filterId);
return meta?.title ?? $t('filter');
@@ -168,6 +128,27 @@
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(createdAt));
const showWorkflowMenu = (event: MouseEvent, workflow: WorkflowResponseDto) => {
const { ToggleEnabled, Edit, Delete } = getWorkflowActions($t, workflow);
void menuManager.show({
target: event.currentTarget as HTMLElement,
position: 'top-left',
items: [
{
...ToggleEnabled,
onAction: () => void onToggleEnabled(workflow),
},
Edit,
getWorkflowShowSchemaAction($t, expandedWorkflows.has(workflow.id), () => toggleShowingSchema(workflow.id)),
MenuItemType.Divider,
{
...Delete,
onAction: () => void onDeleteWorkflow(workflow),
},
],
});
};
</script>
{#snippet chipItem(title: string)}
@@ -232,37 +213,7 @@
color="secondary"
icon={mdiDotsVertical}
aria-label={$t('menu')}
onclick={(event: MouseEvent) => {
void menuManager.show({
target: event.currentTarget as HTMLElement,
position: 'top-left',
items: [
{
title: workflow.enabled ? $t('disable') : $t('enable'),
color: workflow.enabled ? 'danger' : 'primary',
icon: workflow.enabled ? mdiPause : mdiPlay,
onAction: () => void handleToggleEnabled(workflow),
},
{
title: $t('edit'),
icon: mdiPencil,
onAction: () => void handleEditWorkflow(workflow),
},
{
title: expandedWorkflows.has(workflow.id) ? $t('hide_schema') : $t('show_schema'),
icon: mdiCodeJson,
onAction: () => toggleShowingSchema(workflow.id),
},
MenuItemType.Divider,
{
title: $t('delete'),
icon: mdiDelete,
color: 'danger',
onAction: () => void handleDeleteWorkflow(workflow),
},
],
});
}}
onclick={(event: MouseEvent) => showWorkflowMenu(event, workflow)}
/>
</div>
</CardHeader>