diff --git a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx index 18b623bed..803de6149 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-activity-editor-modal/elsa-activity-editor-modal.tsx @@ -1,8 +1,9 @@ -import {Component, Host, h, State} from '@stencil/core'; +import {Component, Host, h, State, Event} from '@stencil/core'; import {eventBus} from '../../../../services/event-bus'; import state from '../../../../utils/store'; import {ActivityDescriptor, ActivityModel, ActivityPropertyDescriptor, EventTypes} from "../../../../models"; import {propertyDisplayManager} from '../../../../services/property-display-manager'; +import {checkBox, FormContext, textArea, textInput} from "../../../../utils/forms"; @Component({ tag: 'elsa-activity-editor-modal', @@ -13,17 +14,21 @@ export class ElsaActivityPickerModal { @State() activityModel: ActivityModel; @State() activityDescriptor: ActivityDescriptor; + @State() selectedTab: string = 'Properties'; dialog: HTMLElsaModalDialogElement; + form: HTMLFormElement; + formContext: FormContext; componentDidLoad() { eventBus.on(EventTypes.ShowActivityEditor, async (activity: ActivityModel, animate: boolean) => { this.activityModel = {...activity, properties: activity.properties || []}; this.activityDescriptor = state.activityDescriptors.find(x => x.type == activity.type); + this.formContext = new FormContext(this.activityModel, newValue => this.activityModel = newValue); await this.dialog.show(animate); }); } - updateActivity(formData: FormData){ + updateActivity(formData: FormData) { const activity = this.activityModel; const activityDescriptor = this.activityDescriptor; const properties: Array = activityDescriptor.properties; @@ -45,17 +50,24 @@ export class ElsaActivityPickerModal { await this.dialog.hide(true); } + onTabClick(e: Event, tab: string) { + e.preventDefault(); + this.selectedTab = tab; + } + render() { + const tabs = ['Properties', 'Common', 'Behaviors']; + const selectedTab = this.selectedTab; + const inactiveClass = 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'; + const selectedClass = 'border-blue-500 text-blue-600'; const activityModel: ActivityModel = this.activityModel || {type: '', activityId: '', outcomes: [], properties: []}; const activityDescriptor = this.activityDescriptor || {displayName: '', type: '', outcomes: [], category: '', traits: 0, browsable: false, properties: [], description: ''}; - const propertyDescriptors: Array = activityDescriptor.properties; - const key = new Date().getTime().toString(); return ( this.dialog = el}>
-
this.onSubmit(e)}> + this.onSubmit(e)} ref={el => this.form = el}>
@@ -68,8 +80,18 @@ export class ElsaActivityPickerModal {

-
- {propertyDescriptors.map(property => this.renderPropertyEditor(activityModel, property))} +
+ +
+ +
+ {this.renderSelectedTab(activityModel, activityDescriptor)}
@@ -97,7 +119,59 @@ export class ElsaActivityPickerModal { ); } + renderSelectedTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) { + return [ + this.renderWorkflowContextTab(activityModel), + this.renderCommonTab(activityModel), + this.renderPropertiesTab(activityModel, activityDescriptor) + ]; + } + + renderWorkflowContextTab(activityModel: ActivityModel) { + const formContext = this.formContext; + + return ( +
+
+ {checkBox(formContext, 'loadWorkflowContext', 'Load Workflow Context', activityModel.loadWorkflowContext, 'When enabled, this will load the workflow context into memory before executing this activity.', 'loadWorkflowContext')} + {checkBox(formContext, 'saveWorkflowContext', 'Save Workflow Context', activityModel.saveWorkflowContext, 'When enabled, this will save the workflow context back into storage after executing this activity.', 'saveWorkflowContext')} + {checkBox(formContext, 'persistWorkflow', 'Save Workflow Instance', activityModel.persistWorkflow, 'When enabled, this will save the workflow instance back into storage right after executing this activity.', 'persistWorkflow')} + {checkBox(formContext, 'persistOutput', 'Save Activity Output', activityModel.persistOutput, 'When enabled, this will store this activity\'s output as part of the workflow instance. Enable this when you plan to reference this output from other activities', 'persistOutput')} +
+
+ ); + } + + renderCommonTab(activityModel: ActivityModel) { + const formContext = this.formContext; + + return ( +
+
+ {textInput(formContext, 'name', 'Name', activityModel.name, 'The technical name of the activity.', 'activityName')} + {textInput(formContext, 'displayName', 'Display Name', activityModel.displayName, 'A friendly name of the activity.', 'activityDisplayName')} + {textArea(formContext, 'description', 'Description', activityModel.description, 'A custom description for this activity', 'activityDescription')} +
+
+ ); + } + + renderPropertiesTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) { + const propertyDescriptors: Array = activityDescriptor.properties; + const key = `activity-settings:${activityModel.activityId}`; + + return ( +
+ {propertyDescriptors.map(property => this.renderPropertyEditor(activityModel, property))} +
+ ); + } + renderPropertyEditor(activity: ActivityModel, property: ActivityPropertyDescriptor) { return propertyDisplayManager.display(activity, property); } + + getHiddenClass(tab: string) { + return this.selectedTab == tab ? '' : 'hidden'; + } } diff --git a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-editor/elsa-workflow-editor.tsx b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-editor/elsa-workflow-editor.tsx index 2d64371eb..4f2bbf80c 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-editor/elsa-workflow-editor.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-editor/elsa-workflow-editor.tsx @@ -147,10 +147,10 @@ export class ElsaWorkflowDefinitionEditor { name: x.name, displayName: x.displayName, description: x.description, - persistWorkflow: false, - loadWorkflowContext: false, - saveWorkflowContext: false, - persistOutput: false, + persistWorkflow: x.persistWorkflow, + loadWorkflowContext: x.loadWorkflowContext, + saveWorkflowContext: x.saveWorkflowContext, + persistOutput: x.persistOutput, properties: x.properties })), connections: workflowModel.connections.map(x => ({ @@ -218,7 +218,11 @@ export class ElsaWorkflowDefinitionEditor { name: source.name, type: source.type, properties: source.properties, - outcomes: [...descriptor.outcomes] + outcomes: [...descriptor.outcomes], + persistOutput: source.persistOutput, + persistWorkflow: source.persistWorkflow, + saveWorkflowContext: source.saveWorkflowContext, + loadWorkflowContext: source.loadWorkflowContext } } diff --git a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-settings-modal/elsa-workflow-settings-modal.tsx b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-settings-modal/elsa-workflow-settings-modal.tsx index 9c1fcbef4..f6865feb6 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-settings-modal/elsa-workflow-settings-modal.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/workflow-editor/elsa-workflow-settings-modal/elsa-workflow-settings-modal.tsx @@ -4,11 +4,7 @@ import {Map} from "../../../../utils/utils"; import {EventTypes, Variables, WorkflowContextFidelity, WorkflowContextOptions, WorkflowDefinition} from "../../../../models"; import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco"; import {MarkerSeverity} from "monaco-editor"; - -interface SelectOption { - value: string; - text: string; -} +import {checkBox, FormContext, selectField, SelectOption, textArea, textInput} from "../../../../utils/forms"; interface VariableDefinition { name?: string; @@ -28,10 +24,12 @@ export class ElsaWorkflowDefinitionSettingsModal { @State() newVariable: VariableDefinition = {}; dialog: HTMLElsaModalDialogElement; monacoEditor: HTMLElsaMonacoElement; + formContext: FormContext; @Watch('workflowDefinition') handleWorkflowDefinitionChanged(newValue: WorkflowDefinition) { this.workflowDefinitionInternal = {...newValue}; + this.formContext = new FormContext(this.workflowDefinitionInternal, newValue => this.workflowDefinitionInternal = newValue); } componentWillLoad() { @@ -42,24 +40,6 @@ export class ElsaWorkflowDefinitionSettingsModal { eventBus.on(EventTypes.ShowWorkflowSettings, async () => await this.dialog.show(true)); } - // Supports hierarchical field names, e.g. 'foo.bar.baz`, which will map to e.g. { foo: { bar: { baz: ''} } }. - updateField(fieldName: string, value: T) { - const model = {...this.workflowDefinitionInternal}; - const fieldNameHierarchy = fieldName.split('.'); - let current = model; - - for (const name of fieldNameHierarchy.slice(0, fieldNameHierarchy.length - 1)) { - if (!current[name]) - current[name] = {}; - - current = current[name]; - } - - const leafFieldName = fieldNameHierarchy.last(); - current[leafFieldName] = value; - this.workflowDefinitionInternal = model; - } - onTabClick(e: Event, tab: string) { e.preventDefault(); this.selectedTab = tab; @@ -75,26 +55,6 @@ export class ElsaWorkflowDefinitionSettingsModal { setTimeout(() => eventBus.emit(EventTypes.UpdateWorkflowSettings, this, this.workflowDefinitionInternal), 250) } - onTextInputChange(e: Event) { - const element = e.target as HTMLInputElement; - this.updateField(element.name, element.value.trim()); - } - - onTextAreaChange(e: Event) { - const element = e.target as HTMLTextAreaElement; - this.updateField(element.name, element.value.trim()); - } - - onCheckBoxChange(e: Event) { - const element = e.target as HTMLInputElement; - this.updateField(element.name, element.checked); - } - - onSelectChange(e: Event) { - const element = e.target as HTMLSelectElement; - this.updateField(element.name, element.value.trim()); - } - onMonacoValueChanged(e: MonacoValueChangedArgs) { // Don't try and parse JSON if it contains errors. const errorCount = e.markers.filter(x => x.severity == MarkerSeverity.Error).length; @@ -115,7 +75,6 @@ export class ElsaWorkflowDefinitionSettingsModal { render() { - const workflowDefinition = this.workflowDefinitionInternal; const tabs = ['Settings', 'Variables', 'Workflow Context']; const selectedTab = this.selectedTab; const inactiveClass = 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'; @@ -179,6 +138,7 @@ export class ElsaWorkflowDefinitionSettingsModal { renderSettingsTab() { const workflowDefinition = this.workflowDefinitionInternal; + const formContext = this.formContext; const persistenceBehaviorOptions: Array = [{ text: 'Suspended', @@ -197,11 +157,11 @@ export class ElsaWorkflowDefinitionSettingsModal { return (
- {this.textInput('name', 'Name', workflowDefinition.name, 'The technical name of the workflow.', 'workflowName')} - {this.textInput('displayName', 'Display Name', workflowDefinition.displayName, 'A user-friendly display name of the workflow.', 'workflowDisplayName')} - {this.textArea('description', 'Description', workflowDefinition.description, null, 'workflowDescription')} - {this.selectField('persistenceBehavior', 'Persistence Behavior', workflowDefinition.persistenceBehavior.toString(), persistenceBehaviorOptions, 'The persistence behavior controls how often a workflow instance is persisted during workflow execution.', 'workflowContextFidelity')} - {this.checkBox('isSingleton', 'Singleton', workflowDefinition.isSingleton, 'Singleton workflows will only have one active instance executing at a time.')} + {textInput(formContext, 'name', 'Name', workflowDefinition.name, 'The technical name of the workflow.', 'workflowName')} + {textInput(formContext, 'displayName', 'Display Name', workflowDefinition.displayName, 'A user-friendly display name of the workflow.', 'workflowDisplayName')} + {textArea(formContext, 'description', 'Description', workflowDefinition.description, null, 'workflowDescription')} + {selectField(formContext, 'persistenceBehavior', 'Persistence Behavior', workflowDefinition.persistenceBehavior.toString(), persistenceBehaviorOptions, 'The persistence behavior controls how often a workflow instance is persisted during workflow execution.', 'workflowContextFidelity')} + {checkBox(formContext, 'isSingleton', 'Singleton', workflowDefinition.isSingleton, 'Singleton workflows will only have one active instance executing at a time.')}
); @@ -225,6 +185,7 @@ export class ElsaWorkflowDefinitionSettingsModal { renderWorkflowContextTab() { const workflowDefinition = this.workflowDefinitionInternal; + const formContext = this.formContext; const contextOptions: WorkflowContextOptions = workflowDefinition.contextOptions || { contextType: undefined, @@ -242,71 +203,10 @@ export class ElsaWorkflowDefinitionSettingsModal { return (
- {this.textInput('contextOptions.contextType', 'Type', contextOptions.contextType, 'The fully qualified workflow context type name.', 'workflowContextType')} - {this.selectField('contextOptions.contextFidelity', 'Fidelity', contextOptions.contextFidelity.toString(), fidelityOptions, 'The workflow context refresh fidelity controls the behavior of when to load and persist the workflow context.', 'workflowContextFidelity')} + {textInput(formContext, 'contextOptions.contextType', 'Type', contextOptions.contextType, 'The fully qualified workflow context type name.', 'workflowContextType')} + {selectField(formContext, 'contextOptions.contextFidelity', 'Fidelity', contextOptions.contextFidelity.toString(), fidelityOptions, 'The workflow context refresh fidelity controls the behavior of when to load and persist the workflow context.', 'workflowContextFidelity')}
); } - - textInput(fieldName: string, label: string, value: string, hint?: string, fieldId?: string) { - fieldId = fieldId || fieldName - return ( -
- -
- this.onTextInputChange(e)} class="focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"/> -
- {hint && hint.length > 0 ?

{hint}

: undefined} -
); - } - - checkBox(fieldName: string, label: string, checked: boolean, hint?: string, fieldId?: string) { - fieldId = fieldId || fieldName - return ( -
-
- this.onCheckBoxChange(e)} class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"/> -
-
- - {hint && hint.length > 0 ?

{hint}

: undefined} -
-
); - } - - textArea(fieldName: string, label: string, value: string, hint?: string, fieldId?: string) { - fieldId = fieldId || fieldName - return ( -
- -
-