diff --git a/i18n/en.json b/i18n/en.json index 97f4575567..054408dff9 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2476,6 +2476,7 @@ "week": "Week", "welcome": "Welcome", "welcome_to_immich": "Welcome to Immich", + "when": "When", "width": "Width", "wifi_name": "Wi-Fi Name", "workflow": "Workflow", diff --git a/web/src/routes/(user)/workflows/[workflowId]/+page.svelte b/web/src/routes/(user)/workflows/[workflowId]/+page.svelte index a7f0c32dca..2fefe06cfa 100644 --- a/web/src/routes/(user)/workflows/[workflowId]/+page.svelte +++ b/web/src/routes/(user)/workflows/[workflowId]/+page.svelte @@ -71,7 +71,7 @@ let isShowingNavigationDialog = $state(false); let isSaving = $state(false); let editMode = $state('visual'); - const workflowSummary = $derived({ trigger, steps }); + const workflowSummary = $derived({ name, description, enabled, trigger, steps }); const workflowJsonContent = $derived({ description, enabled, name, steps, trigger }); const stepsWithConfigEntries = $derived( steps.map((step) => ({ diff --git a/web/src/routes/(user)/workflows/[workflowId]/WorkflowSummary.svelte b/web/src/routes/(user)/workflows/[workflowId]/WorkflowSummary.svelte index a338ec01e5..2e681c258b 100644 --- a/web/src/routes/(user)/workflows/[workflowId]/WorkflowSummary.svelte +++ b/web/src/routes/(user)/workflows/[workflowId]/WorkflowSummary.svelte @@ -3,10 +3,14 @@ import { getTriggerName } from '$lib/utils/workflow'; import type { WorkflowStepDto, WorkflowTrigger } from '@immich/sdk'; import { Icon, IconButton, Text } from '@immich/ui'; - import { mdiClose, mdiFlashOutline, mdiPlayCircleOutline, mdiViewDashboardOutline } from '@mdi/js'; + import { mdiCheck, mdiClose, mdiContentCopy, mdiViewDashboardOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; + import { fly } from 'svelte/transition'; type WorkflowSummaryData = { + name: string | null; + description: string | null; + enabled: boolean; trigger: WorkflowTrigger; steps: WorkflowStepDto[]; }; @@ -16,133 +20,127 @@ }; let { workflow }: Props = $props(); - const { trigger, steps } = $derived(workflow); let isOpen = $state(false); - let position = $state({ x: 0, y: 0 }); - let isDragging = $state(false); - let dragOffset = $state({ x: 0, y: 0 }); - let containerEl: HTMLDivElement | undefined = $state(); + let justCopied = $state(false); + let copyTimer: ReturnType | undefined; - const handleMouseDown = (e: MouseEvent) => { - if (!containerEl) { - return; + const formatConfigValue = (value: unknown): string => { + if (value === null || value === undefined) { + return '—'; } - isDragging = true; - const rect = containerEl.getBoundingClientRect(); - dragOffset = { - x: e.clientX - rect.left, - y: e.clientY - rect.top, - }; - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); + if (typeof value === 'boolean') { + return value ? 'true' : 'false'; + } + if (typeof value === 'number') { + return String(value); + } + if (typeof value === 'string') { + return `"${value}"`; + } + if (Array.isArray(value)) { + if (value.length === 0) { + return '[]'; + } + return '[' + value.map((v) => (v !== null && typeof v === 'object' ? '{…}' : String(v))).join(', ') + ']'; + } + if (typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); }; - const handleMouseMove = (e: MouseEvent) => { - if (!isDragging) { - return; - } - position = { - x: e.clientX - dragOffset.x, - y: e.clientY - dragOffset.y, - }; - }; + const getConfigEntries = (config: WorkflowStepDto['config']) => + Object.entries(config ?? {}).filter(([, value]) => value !== null && value !== undefined && value !== ''); - const handleMouseUp = () => { - isDragging = false; - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - $effect(() => { - // Initialize position to bottom-right on mount - if (globalThis.window && position.x === 0 && position.y === 0) { - position = { - x: globalThis.innerWidth - 280, - y: globalThis.innerHeight - 400, - }; + const asciiSummary = $derived.by(() => { + const lines: string[] = []; + const title = workflow.name ?? $t('no_name'); + const state = workflow.enabled ? '[ON]' : '[OFF]'; + lines.push(`${title} ${state}`); + if (workflow.description) { + lines.push(workflow.description); } + lines.push(''); + lines.push(' WHEN'); + lines.push(` ⚡ ${getTriggerName($t, workflow.trigger)}`); + + lines.push(''); + lines.push(' THEN'); + + if (workflow.steps.length === 0) { + lines.push(` ${$t('no_steps')}`); + return lines.join('\n'); + } + + workflow.steps.forEach((step, i) => { + const method = pluginManager.getMethod(step.method); + const isFilter = method?.uiHints?.includes('filter') ?? false; + const type = isFilter ? 'FILTER' : 'ACTION'; + const label = pluginManager.getMethodLabel(step.method); + lines.push(` [${i + 1}] ${type} · ${label}`); + for (const [key, value] of getConfigEntries(step.config)) { + lines.push(` ${key} = ${formatConfigValue(value)}`); + } + if (i < workflow.steps.length - 1) { + lines.push(''); + } + }); + + return lines.join('\n'); }); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(asciiSummary); + justCopied = true; + if (copyTimer) { + clearTimeout(copyTimer); + } + copyTimer = setTimeout(() => (justCopied = false), 1500); + } catch { + // ignore — clipboard may be unavailable + } + }; {#if isOpen} - -