mirror of
https://github.com/immich-app/immich.git
synced 2025-12-05 20:40:29 -08:00
refactor ActionItem
This commit is contained in:
@@ -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 {
|
import {
|
||||||
|
createWorkflow,
|
||||||
|
deleteWorkflow,
|
||||||
PluginTriggerType,
|
PluginTriggerType,
|
||||||
updateWorkflow as updateWorkflowApi,
|
updateWorkflow,
|
||||||
type PluginActionResponseDto,
|
type PluginActionResponseDto,
|
||||||
type PluginContextType,
|
type PluginContextType,
|
||||||
type PluginFilterResponseDto,
|
type PluginFilterResponseDto,
|
||||||
@@ -10,6 +16,9 @@ import {
|
|||||||
type WorkflowResponseDto,
|
type WorkflowResponseDto,
|
||||||
type WorkflowUpdateDto,
|
type WorkflowUpdateDto,
|
||||||
} from '@immich/sdk';
|
} 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 {
|
export interface WorkflowPayload {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -295,5 +304,108 @@ export const handleUpdateWorkflow = async (
|
|||||||
triggerType,
|
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}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
<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 EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.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 {
|
import {
|
||||||
createWorkflow,
|
getWorkflowActions,
|
||||||
deleteWorkflow,
|
getWorkflowShowSchemaAction,
|
||||||
PluginTriggerType,
|
handleCreateWorkflow,
|
||||||
updateWorkflow,
|
handleDeleteWorkflow,
|
||||||
type PluginFilterResponseDto,
|
handleToggleWorkflowEnabled,
|
||||||
type WorkflowResponseDto,
|
type WorkflowPayload,
|
||||||
} from '@immich/sdk';
|
} from '$lib/services/workflow.service';
|
||||||
|
import type { PluginFilterResponseDto, WorkflowResponseDto } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
@@ -27,12 +24,10 @@
|
|||||||
IconButton,
|
IconButton,
|
||||||
MenuItemType,
|
MenuItemType,
|
||||||
menuManager,
|
menuManager,
|
||||||
modalManager,
|
|
||||||
Text,
|
Text,
|
||||||
toastManager,
|
|
||||||
VStack,
|
VStack,
|
||||||
} from '@immich/ui';
|
} 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 { 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';
|
||||||
@@ -44,6 +39,7 @@
|
|||||||
let { data }: Props = $props();
|
let { data }: Props = $props();
|
||||||
|
|
||||||
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>();
|
||||||
@@ -95,56 +91,20 @@
|
|||||||
|
|
||||||
const getJson = (workflow: WorkflowResponseDto) => JSON.stringify(constructPayload(workflow), null, 2);
|
const getJson = (workflow: WorkflowResponseDto) => JSON.stringify(constructPayload(workflow), null, 2);
|
||||||
|
|
||||||
const handleToggleEnabled = async (workflow: WorkflowResponseDto) => {
|
const onToggleEnabled = async (workflow: WorkflowResponseDto) => {
|
||||||
try {
|
const updated = await handleToggleWorkflowEnabled(workflow);
|
||||||
const updated = await updateWorkflow({
|
if (updated) {
|
||||||
id: workflow.id,
|
|
||||||
workflowUpdateDto: { enabled: !workflow.enabled },
|
|
||||||
});
|
|
||||||
workflows = workflows.map((w) => (w.id === updated.id ? updated : w));
|
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) => {
|
const onDeleteWorkflow = async (workflow: WorkflowResponseDto) => {
|
||||||
try {
|
const deleted = await handleDeleteWorkflow(workflow);
|
||||||
const confirmed = await modalManager.showDialog({
|
if (deleted) {
|
||||||
prompt: $t('workflow_delete_prompt'),
|
|
||||||
confirmColor: 'danger',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteWorkflow({ id: workflow.id });
|
|
||||||
workflows = workflows.filter((w) => w.id !== workflow.id);
|
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 getFilterLabel = (filterId: string) => {
|
||||||
const meta = pluginFilterLookup.get(filterId);
|
const meta = pluginFilterLookup.get(filterId);
|
||||||
return meta?.title ?? $t('filter');
|
return meta?.title ?? $t('filter');
|
||||||
@@ -168,6 +128,27 @@
|
|||||||
dateStyle: 'medium',
|
dateStyle: 'medium',
|
||||||
timeStyle: 'short',
|
timeStyle: 'short',
|
||||||
}).format(new Date(createdAt));
|
}).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>
|
</script>
|
||||||
|
|
||||||
{#snippet chipItem(title: string)}
|
{#snippet chipItem(title: string)}
|
||||||
@@ -232,37 +213,7 @@
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
icon={mdiDotsVertical}
|
icon={mdiDotsVertical}
|
||||||
aria-label={$t('menu')}
|
aria-label={$t('menu')}
|
||||||
onclick={(event: MouseEvent) => {
|
onclick={(event: MouseEvent) => showWorkflowMenu(event, workflow)}
|
||||||
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),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user