Activity editor tabs (#745)
* Add activity editor tabs * WIP: Workflow Context activity tab * Implement common activity settings
This commit is contained in:
parent
6f7d512a9f
commit
f61df66148
|
|
@ -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<ActivityPropertyDescriptor> = 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<ActivityPropertyDescriptor> = activityDescriptor.properties;
|
||||
const key = new Date().getTime().toString();
|
||||
|
||||
return (
|
||||
<Host>
|
||||
<elsa-modal-dialog ref={el => this.dialog = el}>
|
||||
<div slot="content" class="py-8 pb-0">
|
||||
<form onSubmit={e => this.onSubmit(e)}>
|
||||
<form onSubmit={e => this.onSubmit(e)} ref={el => this.form = el}>
|
||||
<div class="flex px-8">
|
||||
<div class="space-y-8 divide-y divide-gray-200 w-full">
|
||||
<div>
|
||||
|
|
@ -68,8 +80,18 @@ export class ElsaActivityPickerModal {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div key={key} class="mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||
{propertyDescriptors.map(property => this.renderPropertyEditor(activityModel, property))}
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
|
||||
{tabs.map(tab => {
|
||||
const isSelected = tab === selectedTab;
|
||||
const cssClass = isSelected ? selectedClass : inactiveClass;
|
||||
return <a href="#" onClick={e => this.onTabClick(e, tab)} class={`${cssClass} whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm`}>{tab}</a>;
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
{this.renderSelectedTab(activityModel, activityDescriptor)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -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 (
|
||||
<div class={`flex ${this.getHiddenClass('Behaviors')}`}>
|
||||
<div class="space-y-8 w-full">
|
||||
{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')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderCommonTab(activityModel: ActivityModel) {
|
||||
const formContext = this.formContext;
|
||||
|
||||
return (
|
||||
<div class={`flex ${this.getHiddenClass('Common')}`}>
|
||||
<div class="space-y-8 w-full">
|
||||
{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')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPropertiesTab(activityModel: ActivityModel, activityDescriptor: ActivityDescriptor) {
|
||||
const propertyDescriptors: Array<ActivityPropertyDescriptor> = activityDescriptor.properties;
|
||||
const key = `activity-settings:${activityModel.activityId}`;
|
||||
|
||||
return (
|
||||
<div key={key} class={`grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6 ${this.getHiddenClass('Properties')}`}>
|
||||
{propertyDescriptors.map(property => this.renderPropertyEditor(activityModel, property))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPropertyEditor(activity: ActivityModel, property: ActivityPropertyDescriptor) {
|
||||
return propertyDisplayManager.display(activity, property);
|
||||
}
|
||||
|
||||
getHiddenClass(tab: string) {
|
||||
return this.selectedTab == tab ? '' : 'hidden';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ConnectionDefinition>(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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T>(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<SelectOption> = [{
|
||||
text: 'Suspended',
|
||||
|
|
@ -197,11 +157,11 @@ export class ElsaWorkflowDefinitionSettingsModal {
|
|||
return (
|
||||
<div class="flex px-8">
|
||||
<div class="space-y-8 w-full">
|
||||
{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.')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -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 (
|
||||
<div class="flex px-8">
|
||||
<div class="space-y-8 w-full">
|
||||
{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')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
textInput(fieldName: string, label: string, value: string, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" id={fieldId} name={fieldName} value={value} onChange={e => 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"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
|
||||
checkBox(fieldName: string, label: string, checked: boolean, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div class="relative flex items-start">
|
||||
<div class="flex items-center h-5">
|
||||
<input id={fieldId} name={fieldName} type="checkbox" value="true" checked={checked} onChange={e => this.onCheckBoxChange(e)} class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"/>
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label htmlFor={fieldId} class="font-medium text-gray-700">{label}</label>
|
||||
{hint && hint.length > 0 ? <p class="text-gray-500">{hint}</p> : undefined}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
textArea(fieldName: string, label: string, value: string, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea id={fieldId} name={fieldName} value={value} onChange={e => this.onTextAreaChange(e)} rows={3} class="focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
|
||||
selectField(fieldName: string, label: string, value: string, options: Array<SelectOption>, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<select id={fieldId} name={fieldName} onChange={e => this.onSelectChange(e)} class="block focus:ring-blue-500 focus:border-blue-500 w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
|
||||
{options.map(item => {
|
||||
const selected = item.value === value;
|
||||
return <option value={item.value} selected={selected}>{item.text}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,27 @@
|
|||
import {ActivityDefinitionProperty, WorkflowPersistenceBehavior} from "./domain";
|
||||
|
||||
export interface WorkflowModel {
|
||||
activities: Array<ActivityModel>
|
||||
connections: Array<ConnectionModel>
|
||||
activities: Array<ActivityModel>;
|
||||
connections: Array<ConnectionModel>;
|
||||
persistenceBehavior?: WorkflowPersistenceBehavior,
|
||||
}
|
||||
|
||||
export interface ActivityModel {
|
||||
activityId: string
|
||||
type: string
|
||||
name?: string
|
||||
displayName?: string
|
||||
description?: string
|
||||
outcomes: Array<string>
|
||||
activityId: string;
|
||||
type: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
outcomes: Array<string>;
|
||||
properties: Array<ActivityDefinitionProperty>;
|
||||
persistWorkflow?: boolean;
|
||||
persistOutput?: boolean;
|
||||
loadWorkflowContext?: boolean;
|
||||
saveWorkflowContext?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionModel {
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
outcome: string
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
outcome: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ export class SendEmailPlugin implements ElsaPlugin {
|
|||
const props = activityModel.properties || [];
|
||||
const condition = props.find(x => x.name == 'To') || { expression: '' };
|
||||
const expression = condition.expression || '';
|
||||
context.bodyDisplay = <p>{expression}</p>;
|
||||
const description = activityModel.description;
|
||||
const bodyText = description && description.length > 0 ? description : expression;
|
||||
context.bodyDisplay = <p>{bodyText}</p>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
117
src/designer/elsa-workflows-studio/src/utils/forms.tsx
Normal file
117
src/designer/elsa-workflows-studio/src/utils/forms.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Supports hierarchical field names, e.g. 'foo.bar.baz`, which will map to e.g. { foo: { bar: { baz: ''} } }.
|
||||
import {Event, h} from "@stencil/core";
|
||||
|
||||
export interface SelectOption {
|
||||
value: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class FormContext {
|
||||
|
||||
constructor(model: any, updater: (model: any) => any) {
|
||||
this.model = model;
|
||||
this.updater = updater;
|
||||
}
|
||||
|
||||
public model: any;
|
||||
public updater: (model: any) => any;
|
||||
}
|
||||
|
||||
export function textInput(context: FormContext, fieldName: string, label: string, value: string, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" id={fieldId} name={fieldName} value={value} onChange={e => onTextInputChange(e, context)} class="focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function checkBox(context: FormContext, fieldName: string, label: string, checked: boolean, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div class="relative flex items-start">
|
||||
<div class="flex items-center h-5">
|
||||
<input id={fieldId} name={fieldName} type="checkbox" value="true" checked={checked} onChange={e => onCheckBoxChange(e, context)} class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"/>
|
||||
</div>
|
||||
<div class="ml-3 text-sm">
|
||||
<label htmlFor={fieldId} class="font-medium text-gray-700">{label}</label>
|
||||
{hint && hint.length > 0 ? <p class="text-gray-500">{hint}</p> : undefined}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function textArea(context: FormContext, fieldName: string, label: string, value: string, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea id={fieldId} name={fieldName} value={value} onChange={e => onTextAreaChange(e, context)} rows={3} class="focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"/>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function selectField(context: FormContext, fieldName: string, label: string, value: string, options: Array<SelectOption>, hint?: string, fieldId?: string) {
|
||||
fieldId = fieldId || fieldName
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={fieldName} class="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<select id={fieldId} name={fieldName} onChange={e => onSelectChange(e, context)} class="block focus:ring-blue-500 focus:border-blue-500 w-full shadow-sm sm:text-sm border-gray-300 rounded-md">
|
||||
{options.map(item => {
|
||||
const selected = item.value === value;
|
||||
return <option value={item.value} selected={selected}>{item.text}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
{hint && hint.length > 0 ? <p class="mt-2 text-sm text-gray-500">{hint}</p> : undefined}
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function updateField<T>(context: FormContext, fieldName: string, value: T) {
|
||||
const fieldNameHierarchy = fieldName.split('.');
|
||||
let current = {...context.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;
|
||||
|
||||
context.model = current;
|
||||
context.updater(current);
|
||||
}
|
||||
|
||||
export function onTextInputChange(e: Event, context: FormContext) {
|
||||
const element = e.target as HTMLInputElement;
|
||||
updateField(context, element.name, element.value.trim());
|
||||
}
|
||||
|
||||
export function onTextAreaChange(e: Event, context: FormContext) {
|
||||
const element = e.target as HTMLTextAreaElement;
|
||||
updateField(context, element.name, element.value.trim());
|
||||
}
|
||||
|
||||
export function onCheckBoxChange(e: Event, context: FormContext) {
|
||||
const element = e.target as HTMLInputElement;
|
||||
updateField(context, element.name, element.checked);
|
||||
}
|
||||
|
||||
export function onSelectChange(e: Event, context: FormContext) {
|
||||
const element = e.target as HTMLSelectElement;
|
||||
updateField(context, element.name, element.value.trim());
|
||||
}
|
||||
Loading…
Reference in a new issue