mirror of
https://github.com/immich-app/immich.git
synced 2025-12-18 17:23:16 +03:00
Compare commits
1 Commits
workflow-u
...
3af09ad140
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3af09ad140 |
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getComponentDefaultValue, getComponentFromSchema } from '$lib/utils/workflow';
|
import { getComponentFromSchema, type ComponentConfig } from '$lib/utils/workflow';
|
||||||
import { Field, Input, MultiSelect, Select, Switch, Text } from '@immich/ui';
|
import { Field, Input, MultiSelect, Select, Switch, Text } from '@immich/ui';
|
||||||
import WorkflowPickerField from './WorkflowPickerField.svelte';
|
import WorkflowPickerField from './WorkflowPickerField.svelte';
|
||||||
|
|
||||||
@@ -25,6 +25,24 @@
|
|||||||
config = configKey ? { ...config, [configKey]: { ...actualConfig, ...updates } } : { ...config, ...updates };
|
config = configKey ? { ...config, [configKey]: { ...actualConfig, ...updates } } : { ...config, ...updates };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper to determine default value for a component based on its type
|
||||||
|
const getDefaultValue = (component: ComponentConfig): unknown => {
|
||||||
|
if (component.defaultValue !== undefined) {
|
||||||
|
return component.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with appropriate empty value based on component type
|
||||||
|
if (component.type === 'multiselect' || (component.type === 'text' && component.subType === 'people-picker')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.type === 'switch') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
// Derive which keys need initialization (missing from actualConfig)
|
// Derive which keys need initialization (missing from actualConfig)
|
||||||
const uninitializedKeys = $derived.by(() => {
|
const uninitializedKeys = $derived.by(() => {
|
||||||
if (!components) {
|
if (!components) {
|
||||||
@@ -33,7 +51,7 @@
|
|||||||
|
|
||||||
return Object.entries(components)
|
return Object.entries(components)
|
||||||
.filter(([key]) => actualConfig[key] === undefined)
|
.filter(([key]) => actualConfig[key] === undefined)
|
||||||
.map(([key, component]) => ({ key, component, defaultValue: getComponentDefaultValue(component) }));
|
.map(([key, component]) => ({ key, component, defaultValue: getDefaultValue(component) }));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Derive the batch updates needed
|
// Derive the batch updates needed
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export const getActionsByContext = (
|
|||||||
return availableActions.filter((action) => action.supportedContexts.includes(context));
|
return availableActions.filter((action) => action.supportedContexts.includes(context));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remap configs when items are reordered (drag-drop)
|
||||||
|
* Moves config from old index to new index position
|
||||||
|
*/
|
||||||
export const remapConfigsOnReorder = (
|
export const remapConfigsOnReorder = (
|
||||||
configs: Record<string, unknown>,
|
configs: Record<string, unknown>,
|
||||||
prefix: 'filter' | 'action',
|
prefix: 'filter' | 'action',
|
||||||
@@ -107,19 +111,30 @@ export const remapConfigsOnRemove = (
|
|||||||
return newConfigs;
|
return newConfigs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initializeConfigs = (
|
/**
|
||||||
type: 'action' | 'filter',
|
* Initialize filter configurations from existing workflow
|
||||||
workflow: WorkflowResponseDto,
|
* Uses index-based keys to support multiple filters of the same type
|
||||||
): Record<string, unknown> => {
|
*/
|
||||||
|
export const initializeFilterConfigs = (workflow: WorkflowResponseDto): Record<string, unknown> => {
|
||||||
const configs: Record<string, unknown> = {};
|
const configs: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (workflow.filters && type == 'filter') {
|
if (workflow.filters) {
|
||||||
for (const [index, workflowFilter] of workflow.filters.entries()) {
|
for (const [index, workflowFilter] of workflow.filters.entries()) {
|
||||||
configs[`filter_${index}`] = workflowFilter.filterConfig ?? {};
|
configs[`filter_${index}`] = workflowFilter.filterConfig ?? {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflow.actions && type == 'action') {
|
return configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize action configurations from existing workflow
|
||||||
|
* Uses index-based keys to support multiple actions of the same type
|
||||||
|
*/
|
||||||
|
export const initializeActionConfigs = (workflow: WorkflowResponseDto): Record<string, unknown> => {
|
||||||
|
const configs: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if (workflow.actions) {
|
||||||
for (const [index, workflowAction] of workflow.actions.entries()) {
|
for (const [index, workflowAction] of workflow.actions.entries()) {
|
||||||
configs[`action_${index}`] = workflowAction.actionConfig ?? {};
|
configs[`action_${index}`] = workflowAction.actionConfig ?? {};
|
||||||
}
|
}
|
||||||
@@ -160,6 +175,9 @@ export const buildWorkflowPayload = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JSON workflow and update state
|
||||||
|
*/
|
||||||
export const parseWorkflowJson = (
|
export const parseWorkflowJson = (
|
||||||
jsonString: string,
|
jsonString: string,
|
||||||
availableTriggers: PluginTriggerResponseDto[],
|
availableTriggers: PluginTriggerResponseDto[],
|
||||||
@@ -182,8 +200,10 @@ export const parseWorkflowJson = (
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonString);
|
const parsed = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
// Find trigger
|
||||||
const trigger = availableTriggers.find((t) => t.type === parsed.triggerType);
|
const trigger = availableTriggers.find((t) => t.type === parsed.triggerType);
|
||||||
|
|
||||||
|
// Parse filters (using index-based keys to support multiple of same type)
|
||||||
const filters: PluginFilterResponseDto[] = [];
|
const filters: PluginFilterResponseDto[] = [];
|
||||||
const filterConfigs: Record<string, unknown> = {};
|
const filterConfigs: Record<string, unknown> = {};
|
||||||
if (Array.isArray(parsed.filters)) {
|
if (Array.isArray(parsed.filters)) {
|
||||||
@@ -197,6 +217,7 @@ export const parseWorkflowJson = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse actions (using index-based keys to support multiple of same type)
|
||||||
const actions: PluginActionResponseDto[] = [];
|
const actions: PluginActionResponseDto[] = [];
|
||||||
const actionConfigs: Record<string, unknown> = {};
|
const actionConfigs: Record<string, unknown> = {};
|
||||||
if (Array.isArray(parsed.actions)) {
|
if (Array.isArray(parsed.actions)) {
|
||||||
@@ -231,6 +252,9 @@ export const parseWorkflowJson = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if workflow has changes compared to previous version
|
||||||
|
*/
|
||||||
export const hasWorkflowChanged = (
|
export const hasWorkflowChanged = (
|
||||||
previousWorkflow: WorkflowResponseDto,
|
previousWorkflow: WorkflowResponseDto,
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
@@ -242,30 +266,36 @@ export const hasWorkflowChanged = (
|
|||||||
filterConfigs: Record<string, unknown>,
|
filterConfigs: Record<string, unknown>,
|
||||||
actionConfigs: Record<string, unknown>,
|
actionConfigs: Record<string, unknown>,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
// Check enabled state
|
||||||
if (enabled !== previousWorkflow.enabled) {
|
if (enabled !== previousWorkflow.enabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check name or description
|
||||||
if (name !== (previousWorkflow.name ?? '') || description !== (previousWorkflow.description ?? '')) {
|
if (name !== (previousWorkflow.name ?? '') || description !== (previousWorkflow.description ?? '')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check trigger
|
||||||
if (triggerType !== previousWorkflow.triggerType) {
|
if (triggerType !== previousWorkflow.triggerType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check filters order/items
|
||||||
const previousFilterIds = previousWorkflow.filters?.map((f) => f.pluginFilterId) ?? [];
|
const previousFilterIds = previousWorkflow.filters?.map((f) => f.pluginFilterId) ?? [];
|
||||||
const currentFilterIds = orderedFilters.map((f) => f.id);
|
const currentFilterIds = orderedFilters.map((f) => f.id);
|
||||||
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check actions order/items
|
||||||
const previousActionIds = previousWorkflow.actions?.map((a) => a.pluginActionId) ?? [];
|
const previousActionIds = previousWorkflow.actions?.map((a) => a.pluginActionId) ?? [];
|
||||||
const currentActionIds = orderedActions.map((a) => a.id);
|
const currentActionIds = orderedActions.map((a) => a.id);
|
||||||
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check filter configs (using index-based keys)
|
||||||
const previousFilterConfigs: Record<string, unknown> = {};
|
const previousFilterConfigs: Record<string, unknown> = {};
|
||||||
for (const [index, wf] of (previousWorkflow.filters ?? []).entries()) {
|
for (const [index, wf] of (previousWorkflow.filters ?? []).entries()) {
|
||||||
previousFilterConfigs[`filter_${index}`] = wf.filterConfig ?? {};
|
previousFilterConfigs[`filter_${index}`] = wf.filterConfig ?? {};
|
||||||
@@ -274,6 +304,7 @@ export const hasWorkflowChanged = (
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check action configs (using index-based keys)
|
||||||
const previousActionConfigs: Record<string, unknown> = {};
|
const previousActionConfigs: Record<string, unknown> = {};
|
||||||
for (const [index, wa] of (previousWorkflow.actions ?? []).entries()) {
|
for (const [index, wa] of (previousWorkflow.actions ?? []).entries()) {
|
||||||
previousActionConfigs[`action_${index}`] = wa.actionConfig ?? {};
|
previousActionConfigs[`action_${index}`] = wa.actionConfig ?? {};
|
||||||
@@ -285,6 +316,9 @@ export const hasWorkflowChanged = (
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a workflow via API
|
||||||
|
*/
|
||||||
export const handleUpdateWorkflow = async (
|
export const handleUpdateWorkflow = async (
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
|||||||
@@ -28,22 +28,6 @@ interface JSONSchema {
|
|||||||
required?: string[];
|
required?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getComponentDefaultValue = (component: ComponentConfig): unknown => {
|
|
||||||
if (component.defaultValue !== undefined) {
|
|
||||||
return component.defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.type === 'multiselect' || (component.type === 'text' && component.subType === 'people-picker')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.type === 'switch') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getComponentFromSchema = (schema: object | null): Record<string, ComponentConfig> | null => {
|
export const getComponentFromSchema = (schema: object | null): Record<string, ComponentConfig> | null => {
|
||||||
if (!schema || !isJSONSchema(schema) || !schema.properties) {
|
if (!schema || !isJSONSchema(schema) || !schema.properties) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
getFiltersByContext,
|
getFiltersByContext,
|
||||||
handleUpdateWorkflow,
|
handleUpdateWorkflow,
|
||||||
hasWorkflowChanged,
|
hasWorkflowChanged,
|
||||||
initializeConfigs,
|
initializeActionConfigs,
|
||||||
|
initializeFilterConfigs,
|
||||||
parseWorkflowJson,
|
parseWorkflowJson,
|
||||||
remapConfigsOnRemove,
|
remapConfigsOnRemove,
|
||||||
remapConfigsOnReorder,
|
remapConfigsOnReorder,
|
||||||
@@ -94,8 +95,8 @@
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
let filterConfigs: Record<string, unknown> = $derived(initializeConfigs('filter', editWorkflow));
|
let filterConfigs: Record<string, unknown> = $derived(initializeFilterConfigs(editWorkflow));
|
||||||
let actionConfigs: Record<string, unknown> = $derived(initializeConfigs('action', editWorkflow));
|
let actionConfigs: Record<string, unknown> = $derived(initializeActionConfigs(editWorkflow));
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
editWorkflow.triggerType = triggerType;
|
editWorkflow.triggerType = triggerType;
|
||||||
@@ -128,6 +129,7 @@
|
|||||||
actionConfigs,
|
actionConfigs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update the previous workflow state to the new values
|
||||||
previousWorkflow = updated;
|
previousWorkflow = updated;
|
||||||
editWorkflow = updated;
|
editWorkflow = updated;
|
||||||
|
|
||||||
@@ -198,6 +200,7 @@
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Drag and drop handlers
|
||||||
let draggedFilterIndex: number | null = $state(null);
|
let draggedFilterIndex: number | null = $state(null);
|
||||||
let draggedActionIndex: number | null = $state(null);
|
let draggedActionIndex: number | null = $state(null);
|
||||||
let dragOverFilterIndex: number | null = $state(null);
|
let dragOverFilterIndex: number | null = $state(null);
|
||||||
@@ -249,6 +252,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remap configs to follow the new order
|
||||||
actionConfigs = remapConfigsOnReorder(actionConfigs, 'action', draggedActionIndex, index, selectedActions.length);
|
actionConfigs = remapConfigsOnReorder(actionConfigs, 'action', draggedActionIndex, index, selectedActions.length);
|
||||||
|
|
||||||
const newActions = [...selectedActions];
|
const newActions = [...selectedActions];
|
||||||
@@ -262,12 +266,12 @@
|
|||||||
dragOverActionIndex = null;
|
dragOverActionIndex = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddStep = async (type: 'action' | 'filter') => {
|
const handleAddStep = async (type?: 'action' | 'filter') => {
|
||||||
const result = await modalManager.show(AddWorkflowStepModal, {
|
const result = (await modalManager.show(AddWorkflowStepModal, {
|
||||||
filters: supportFilters,
|
filters: supportFilters,
|
||||||
actions: supportActions,
|
actions: supportActions,
|
||||||
type,
|
type,
|
||||||
});
|
})) as { type: 'filter' | 'action'; item: PluginFilterResponseDto | PluginActionResponseDto } | undefined;
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.type === 'filter') {
|
if (result.type === 'filter') {
|
||||||
@@ -279,11 +283,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFilter = (index: number) => {
|
const handleRemoveFilter = (index: number) => {
|
||||||
|
// Remap configs to account for the removed item
|
||||||
filterConfigs = remapConfigsOnRemove(filterConfigs, 'filter', index, selectedFilters.length);
|
filterConfigs = remapConfigsOnRemove(filterConfigs, 'filter', index, selectedFilters.length);
|
||||||
selectedFilters = selectedFilters.filter((_, i) => i !== index);
|
selectedFilters = selectedFilters.filter((_, i) => i !== index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveAction = (index: number) => {
|
const handleRemoveAction = (index: number) => {
|
||||||
|
// Remap configs to account for the removed item
|
||||||
actionConfigs = remapConfigsOnRemove(actionConfigs, 'action', index, selectedActions.length);
|
actionConfigs = remapConfigsOnRemove(actionConfigs, 'action', index, selectedActions.length);
|
||||||
selectedActions = selectedActions.filter((_, i) => i !== index);
|
selectedActions = selectedActions.filter((_, i) => i !== index);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user