Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Tran
3af09ad140 fix: new schemaformfield has value of the same type 2025-12-05 21:09:05 +00:00
4 changed files with 72 additions and 30 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);
}; };