Simple design for empty node (#15087)
I did an acceptable design for empty node and iterators for release: - use array field for iterator node. Added an util to stringify arrays for backward compatibily - remove icon for empty node - allow to select a node on empty node selection https://github.com/user-attachments/assets/b00037a8-aa1d-4784-b973-05973649b46e
This commit is contained in:
-1
@@ -250,7 +250,6 @@ export const WorkflowRunStepNodeDetail = ({
|
||||
return (
|
||||
<WorkflowEditActionEmpty
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={{
|
||||
readonly: true,
|
||||
}}
|
||||
|
||||
+1
-7
@@ -224,13 +224,7 @@ export const WorkflowStepDetail = ({
|
||||
);
|
||||
}
|
||||
case 'EMPTY': {
|
||||
return (
|
||||
<WorkflowEditActionEmpty
|
||||
key={stepId}
|
||||
action={stepDefinition.definition}
|
||||
actionOptions={props}
|
||||
/>
|
||||
);
|
||||
return <WorkflowEditActionEmpty key={stepId} actionOptions={props} />;
|
||||
}
|
||||
default:
|
||||
return assertUnreachable(
|
||||
|
||||
+6
-36
@@ -1,12 +1,8 @@
|
||||
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
|
||||
import { CommandMenuWorkflowSelectAction } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
|
||||
import { CommandMenuWorkflowEditStepType } from '@/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStepType';
|
||||
import { type WorkflowEmptyAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowActionFooter } from '@/workflow/workflow-steps/components/WorkflowActionFooter';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
type WorkflowEditActionEmptyProps = {
|
||||
action: WorkflowEmptyAction;
|
||||
actionOptions:
|
||||
| {
|
||||
readonly: true;
|
||||
@@ -18,37 +14,11 @@ type WorkflowEditActionEmptyProps = {
|
||||
};
|
||||
|
||||
export const WorkflowEditActionEmpty = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionEmptyProps) => {
|
||||
const { getIcon } = useIcons();
|
||||
const { headerTitle, headerIcon, headerIconColor, headerType } =
|
||||
useWorkflowActionHeader({
|
||||
action,
|
||||
defaultTitle: 'Empty Node',
|
||||
});
|
||||
if (actionOptions.readonly === true) {
|
||||
return <CommandMenuWorkflowSelectAction onActionSelected={() => {}} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidePanelHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionOptions.onActionUpdate({
|
||||
...action,
|
||||
name: newName,
|
||||
});
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>{'Empty Node'}</WorkflowStepBody>
|
||||
{!actionOptions.readonly && <WorkflowActionFooter stepId={action.id} />}
|
||||
</>
|
||||
);
|
||||
return <CommandMenuWorkflowEditStepType />;
|
||||
};
|
||||
|
||||
+38
-9
@@ -1,11 +1,15 @@
|
||||
import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
||||
import { FormArrayFieldInput } from '@/object-record/record-field/ui/form-types/components/FormArrayFieldInput';
|
||||
import { type FieldArrayValue } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { type WorkflowIteratorAction } from '@/workflow/types/Workflow';
|
||||
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isArray, isString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
type WorkflowEditActionIteratorProps = {
|
||||
@@ -20,6 +24,20 @@ type WorkflowEditActionIteratorProps = {
|
||||
};
|
||||
};
|
||||
|
||||
const stringifyArrayItems = (array: FieldArrayValue) => {
|
||||
return array.map((item) => {
|
||||
if (!isDefined(item)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isString(item)) {
|
||||
return item;
|
||||
}
|
||||
|
||||
return JSON.stringify(item);
|
||||
});
|
||||
};
|
||||
|
||||
export const WorkflowEditActionIterator = ({
|
||||
action,
|
||||
actionOptions,
|
||||
@@ -32,8 +50,18 @@ export const WorkflowEditActionIterator = ({
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const defaultItems = isDefined(action.settings.input.items)
|
||||
? action.settings.input.items
|
||||
: [];
|
||||
|
||||
const parsedItems = isStandaloneVariableString(defaultItems)
|
||||
? defaultItems
|
||||
: isArray(defaultItems)
|
||||
? stringifyArrayItems(defaultItems)
|
||||
: stringifyArrayItems(JSON.parse(defaultItems));
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
items: action.settings.input.items || [],
|
||||
items: parsedItems,
|
||||
initialLoopStepIds: action.settings.input.initialLoopStepIds || [],
|
||||
});
|
||||
|
||||
@@ -57,7 +85,10 @@ export const WorkflowEditActionIterator = ({
|
||||
1000,
|
||||
);
|
||||
|
||||
const handleFieldChange = (field: string, value: any) => {
|
||||
const handleFieldChange = (
|
||||
field: string,
|
||||
value: string | FieldArrayValue,
|
||||
) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
@@ -89,15 +120,13 @@ export const WorkflowEditActionIterator = ({
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
<FormTextFieldInput
|
||||
<FormArrayFieldInput
|
||||
label={t`Items to iterate over`}
|
||||
placeholder={t`Enter array of items or variable expression`}
|
||||
defaultValue={
|
||||
Array.isArray(formData.items)
|
||||
? JSON.stringify(formData.items)
|
||||
: (formData.items as string) || ''
|
||||
defaultValue={formData.items}
|
||||
onChange={(value: string | FieldArrayValue) =>
|
||||
handleFieldChange('items', value)
|
||||
}
|
||||
onChange={(value: string) => handleFieldChange('items', value)}
|
||||
readonly={actionOptions.readonly}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
/>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export const getActionHeaderTypeOrThrow = (actionType: WorkflowActionType) => {
|
||||
return msg`Iterator`;
|
||||
}
|
||||
case 'EMPTY': {
|
||||
return msg`Empty Node`;
|
||||
return msg`Add an Action`;
|
||||
}
|
||||
default:
|
||||
assertUnreachable(actionType, `Unsupported action type: ${actionType}`);
|
||||
|
||||
+2
@@ -23,6 +23,8 @@ export const getActionIcon = (actionType: WorkflowActionType) => {
|
||||
return HUMAN_INPUT_ACTIONS.find((item) => item.type === actionType)?.icon;
|
||||
case 'ITERATOR':
|
||||
return 'IconRepeat';
|
||||
case 'EMPTY':
|
||||
return 'IconSettingsAutomation';
|
||||
default:
|
||||
return 'IconDefault';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user