diff --git a/src/core/Elsa.Abstractions/Attributes/ActivityPropertyAttribute.cs b/src/core/Elsa.Abstractions/Attributes/ActivityPropertyAttribute.cs index 0c81638e5..2c8da6441 100644 --- a/src/core/Elsa.Abstractions/Attributes/ActivityPropertyAttribute.cs +++ b/src/core/Elsa.Abstractions/Attributes/ActivityPropertyAttribute.cs @@ -29,5 +29,10 @@ namespace Elsa.Attributes /// A value representing options specific to a given UI hint. /// public object? Options { get; set; } + + /// + /// The name of a static method that provides options. + /// + public string? OptionsProvider { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Abstractions/Design/ActivityPropertyUIHints.cs b/src/core/Elsa.Abstractions/Design/ActivityPropertyUIHints.cs index 14d687349..8accafebf 100644 --- a/src/core/Elsa.Abstractions/Design/ActivityPropertyUIHints.cs +++ b/src/core/Elsa.Abstractions/Design/ActivityPropertyUIHints.cs @@ -9,6 +9,7 @@ namespace Elsa.Design public const string RadioList = "radio-list"; public const string Dropdown = "dropdown"; public const string MultiText = "multi-text"; + public const string CodeEditor = "code-editor"; /// /// A list that allows the user to enter zero or more values. diff --git a/src/core/Elsa.Core/Metadata/ActivityDescriber.cs b/src/core/Elsa.Core/Metadata/ActivityDescriber.cs index 09a07238f..79fe9df19 100644 --- a/src/core/Elsa.Core/Metadata/ActivityDescriber.cs +++ b/src/core/Elsa.Core/Metadata/ActivityDescriber.cs @@ -57,17 +57,30 @@ namespace Elsa.Metadata if (activityProperty == null) continue; + var options = activityProperty.OptionsProvider is not null and not "" ? GetOptions(activityType, activityProperty.OptionsProvider) : activityProperty.Options; + yield return new ActivityPropertyDescriptor ( (activityProperty.Name ?? propertyInfo.Name).Pascalize(), (activityProperty.UIHint ?? InferPropertyUIHint(propertyInfo)), activityProperty.Label ?? propertyInfo.Name.Humanize(LetterCasing.Title), activityProperty.Hint, - activityProperty.Options + options ); } } + private object? GetOptions(Type activityType, string providerMethodName) + { + var providerMethod = activityType.GetMethod(providerMethodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (providerMethod == null) + throw new MissingMethodException(activityType.Name, providerMethodName); + + var options = providerMethod.Invoke(null, new object[0]); + return options; + } + private string InferPropertyUIHint(PropertyInfo propertyInfo) { var type = propertyInfo.PropertyType; diff --git a/src/designer/elsa-workflows-studio/src/components.d.ts b/src/designer/elsa-workflows-studio/src/components.d.ts index 5f5405abf..a0a0f5134 100644 --- a/src/designer/elsa-workflows-studio/src/components.d.ts +++ b/src/designer/elsa-workflows-studio/src/components.d.ts @@ -52,7 +52,18 @@ export namespace Components { "propertyDescriptor": ActivityPropertyDescriptor; "propertyModel": ActivityDefinitionProperty; } + interface ElsaScriptProperty { + "context"?: string; + "editorHeight": string; + "propertyDescriptor": ActivityPropertyDescriptor; + "propertyModel": ActivityDefinitionProperty; + "serverUrl": string; + "singleLineMode": boolean; + "syntax"?: string; + "workflowDefinitionId": string; + } interface ElsaTextProperty { + "context"?: string; "editorHeight": string; "propertyDescriptor": ActivityPropertyDescriptor; "propertyModel": ActivityDefinitionProperty; @@ -139,6 +150,12 @@ declare global { prototype: HTMLElsaMultiTextPropertyElement; new (): HTMLElsaMultiTextPropertyElement; }; + interface HTMLElsaScriptPropertyElement extends Components.ElsaScriptProperty, HTMLStencilElement { + } + var HTMLElsaScriptPropertyElement: { + prototype: HTMLElsaScriptPropertyElement; + new (): HTMLElsaScriptPropertyElement; + }; interface HTMLElsaTextPropertyElement extends Components.ElsaTextProperty, HTMLStencilElement { } var HTMLElsaTextPropertyElement: { @@ -175,6 +192,7 @@ declare global { "elsa-modal-dialog": HTMLElsaModalDialogElement; "elsa-monaco": HTMLElsaMonacoElement; "elsa-multi-text-property": HTMLElsaMultiTextPropertyElement; + "elsa-script-property": HTMLElsaScriptPropertyElement; "elsa-text-property": HTMLElsaTextPropertyElement; "elsa-workflow-editor": HTMLElsaWorkflowEditorElement; "elsa-workflow-publish-button": HTMLElsaWorkflowPublishButtonElement; @@ -227,7 +245,18 @@ declare namespace LocalJSX { "propertyDescriptor"?: ActivityPropertyDescriptor; "propertyModel"?: ActivityDefinitionProperty; } + interface ElsaScriptProperty { + "context"?: string; + "editorHeight"?: string; + "propertyDescriptor"?: ActivityPropertyDescriptor; + "propertyModel"?: ActivityDefinitionProperty; + "serverUrl"?: string; + "singleLineMode"?: boolean; + "syntax"?: string; + "workflowDefinitionId"?: string; + } interface ElsaTextProperty { + "context"?: string; "editorHeight"?: string; "propertyDescriptor"?: ActivityPropertyDescriptor; "propertyModel"?: ActivityDefinitionProperty; @@ -257,6 +286,7 @@ declare namespace LocalJSX { "elsa-modal-dialog": ElsaModalDialog; "elsa-monaco": ElsaMonaco; "elsa-multi-text-property": ElsaMultiTextProperty; + "elsa-script-property": ElsaScriptProperty; "elsa-text-property": ElsaTextProperty; "elsa-workflow-editor": ElsaWorkflowEditor; "elsa-workflow-publish-button": ElsaWorkflowPublishButton; @@ -278,6 +308,7 @@ declare module "@stencil/core" { "elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes; "elsa-monaco": LocalJSX.ElsaMonaco & JSXBase.HTMLAttributes; "elsa-multi-text-property": LocalJSX.ElsaMultiTextProperty & JSXBase.HTMLAttributes; + "elsa-script-property": LocalJSX.ElsaScriptProperty & JSXBase.HTMLAttributes; "elsa-text-property": LocalJSX.ElsaTextProperty & JSXBase.HTMLAttributes; "elsa-workflow-editor": LocalJSX.ElsaWorkflowEditor & JSXBase.HTMLAttributes; "elsa-workflow-publish-button": LocalJSX.ElsaWorkflowPublishButton & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree-activity/elsa-designer-tree-activity.tsx b/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree-activity/elsa-designer-tree-activity.tsx index 0669971e8..10fe31003 100644 --- a/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree-activity/elsa-designer-tree-activity.tsx +++ b/src/designer/elsa-workflows-studio/src/components/designers/tree/elsa-designer-tree-activity/elsa-designer-tree-activity.tsx @@ -51,7 +51,7 @@ export class ElsaDesignerTreeActivity { return ( -
+
{displayContext.activityIcon} @@ -109,7 +109,7 @@ export class ElsaDesignerTreeActivity { return undefined; return ( -
+
{this.displayContext.bodyDisplay}
) diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.css b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.css new file mode 100644 index 000000000..5d4e87f30 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.css @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.tsx new file mode 100644 index 000000000..1d5618a0a --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/elsa-script-property.tsx @@ -0,0 +1,99 @@ +import {Component, h, Prop, State} from '@stencil/core'; +import {ActivityDefinitionProperty, ActivityPropertyDescriptor} from "../../../../models"; +import {createElsaClient} from "../../../../services/elsa-client"; +import Tunnel from '../../../data/workflow-editor'; +import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco"; + +@Component({ + tag: 'elsa-script-property', + styleUrl: 'elsa-script-property.css', + shadow: false, +}) +export class ElsaScriptProperty { + + @Prop() propertyDescriptor: ActivityPropertyDescriptor; + @Prop() propertyModel: ActivityDefinitionProperty; + @Prop({attribute: 'editor-height', reflect: true}) editorHeight: string = '6em'; + @Prop({attribute: 'single-line', reflect: true}) singleLineMode: boolean = false; + @Prop({attribute: 'context', reflect: true}) context?: string; + @Prop() syntax?: string; + @Prop({mutable: true}) serverUrl: string; + @Prop({mutable: true}) workflowDefinitionId: string; + @State() currentValue?: string + monacoEditor: HTMLElsaMonacoElement; + + async componentWillLoad() { + this.currentValue = this.propertyModel.expression; + } + + async componentDidLoad() { + const elsaClient = createElsaClient(this.serverUrl); + const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context); + const libUri = 'defaultLib:lib.es6.d.ts'; + await this.monacoEditor.addJavaScriptLib(libSource, libUri); + } + + mapSyntaxToLanguage(syntax: string): any { + switch (syntax) { + case 'JavaScript': + return 'javascript'; + case 'Liquid': + return 'handlebars'; + case 'Literal': + default: + return 'plaintext'; + } + } + + onMonacoValueChanged(e: MonacoValueChangedArgs) { + this.currentValue = e.value; + } + + render() { + const propertyDescriptor = this.propertyDescriptor; + const options = propertyDescriptor.options || {}; + const syntax = this.syntax; + const monacoLanguage = this.mapSyntaxToLanguage(syntax); + const propertyName = propertyDescriptor.name; + const fieldId = propertyName; + const fieldName = propertyName; + const fieldLabel = propertyDescriptor.label || propertyName; + const fieldHint = propertyDescriptor.hint; + const property = this.propertyModel; + const value = this.currentValue; + + return
+ +
+
+ +
+
+
+ +
+
+
+
+ this.onMonacoValueChanged(e.detail)} + ref={el => this.monacoEditor = el}/> +
+ {fieldHint ?

{fieldHint}

: undefined} + +
+ } +} + +Tunnel.injectProps(ElsaScriptProperty, ['serverUrl', 'workflowDefinitionId']); diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/readme.md b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/readme.md new file mode 100644 index 000000000..3c564bdc9 --- /dev/null +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-script-property/readme.md @@ -0,0 +1,37 @@ +# elsa-modal-dialog + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------------------- | ------------------------ | ----------- | ---------------------------- | ----------- | +| `editorHeight` | `editor-height` | | `string` | `'6em'` | +| `propertyDescriptor` | -- | | `ActivityPropertyDescriptor` | `undefined` | +| `propertyModel` | -- | | `ActivityDefinitionProperty` | `undefined` | +| `serverUrl` | `server-url` | | `string` | `undefined` | +| `singleLineMode` | `single-line` | | `boolean` | `false` | +| `workflowDefinitionId` | `workflow-definition-id` | | `string` | `undefined` | + + +## Dependencies + +### Depends on + +- [elsa-monaco](../../monaco/elsa-monaco) +- context-consumer + +### Graph +```mermaid +graph TD; + elsa-text-property --> elsa-monaco + elsa-text-property --> context-consumer + style elsa-text-property fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-text-property/elsa-text-property.tsx b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-text-property/elsa-text-property.tsx index e4381ab6f..6ec9058fc 100644 --- a/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-text-property/elsa-text-property.tsx +++ b/src/designer/elsa-workflows-studio/src/components/editors/properties/elsa-text-property/elsa-text-property.tsx @@ -15,6 +15,7 @@ export class ElsaTextProperty { @Prop() propertyModel: ActivityDefinitionProperty; @Prop({attribute: 'editor-height', reflect: true}) editorHeight: string = '6em'; @Prop({attribute: 'single-line', reflect: true}) singleLineMode: boolean = false; + @Prop({attribute: 'context', reflect: true}) context?: string; @Prop({mutable: true}) serverUrl: string; @Prop({mutable: true}) workflowDefinitionId: string; @State() selectedSyntax?: string; @@ -28,7 +29,7 @@ export class ElsaTextProperty { async componentDidLoad() { const elsaClient = createElsaClient(this.serverUrl); - const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId); + const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context); const libUri = 'defaultLib:lib.es6.d.ts'; await this.monacoEditor.addJavaScriptLib(libSource, libUri); } @@ -59,19 +60,19 @@ export class ElsaTextProperty { } render() { - const syntaxes = ['Literal', 'JavaScript', 'Liquid'].reverse(); - const selectedSyntax = this.selectedSyntax ?? 'Literal'; - const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax); const propertyDescriptor = this.propertyDescriptor; + const options = propertyDescriptor.options || {}; + const syntaxes = options.syntaxes || ['Literal', 'JavaScript', 'Liquid']; + const selectedSyntax = this.selectedSyntax ?? (syntaxes.length > 0 ? syntaxes[0] : ''); + const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax); const propertyName = propertyDescriptor.name; const fieldId = propertyName; const fieldName = propertyName; const fieldLabel = propertyDescriptor.label || propertyName; const fieldHint = propertyDescriptor.hint; - const syntaxFieldId = `${fieldId}Syntax`; const syntaxFieldName = `${fieldName}Syntax`; - const property = this.propertyModel; const value = this.currentValue; + const reversedSyntaxes = [...syntaxes].reverse(); return
@@ -86,18 +87,18 @@ export class ElsaTextProperty {