diff --git a/src/core/Elsa.Core/Expressions/ElsaExpression.cs b/src/core/Elsa.Core/Expressions/ElsaExpression.cs index 97c32d5cd..23f739bef 100644 --- a/src/core/Elsa.Core/Expressions/ElsaExpression.cs +++ b/src/core/Elsa.Core/Expressions/ElsaExpression.cs @@ -5,12 +5,12 @@ namespace Elsa.Expressions; public class ElsaExpression : IExpression { - public ElsaExpression(string scriptExpression) + public ElsaExpression(string value) { - ScriptExpression = scriptExpression; + Value = value; } - public string ScriptExpression { get; } + public string Value { get; } } public class ElsaExpressionHandler : IExpressionHandler @@ -20,6 +20,6 @@ public class ElsaExpressionHandler : IExpressionHandler var elsaExpression = (ElsaExpression)expression; // TODO: evaluate expression. - return new ValueTask(elsaExpression.ScriptExpression); + return new ValueTask(elsaExpression.Value); } } \ No newline at end of file diff --git a/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs index 1b941f18c..603431c0c 100644 --- a/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs +++ b/src/core/Elsa.Management/Serialization/Converters/InputJsonConverter.cs @@ -33,7 +33,7 @@ public class InputJsonConverter : JsonConverter> if (!expressionElement.TryGetProperty("type", out var expressionTypeNameElement)) throw new JsonException("Failed to extract expression type property"); - var expressionTypeName = expressionTypeNameElement.GetString()!; + var expressionTypeName = expressionTypeNameElement.GetString() ?? "Literal"; var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(expressionTypeName); if (expressionSyntaxDescriptor == null) diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index 5f0e1d6e1..62d06928e 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -5,13 +5,14 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, SelectListItem, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models"; +import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models"; import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./components/designer/activity-properties-editor/activity-properties-editor"; import { AddActivityArgs } from "./components/designer/canvas/canvas"; import { NodeInputContext } from "./services/node-input-driver"; import { MenuItem } from "./components/shared/context-menu/models"; import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models"; import { AddActivityArgs as AddActivityArgs1 } from "./components/designer/canvas/canvas"; +import { ExpressionChangedArs } from "./components/designer/input-control-switch/input-control-switch"; import { MonacoLib, MonacoValueChangedArgs } from "./components/shared/monaco-editor/monaco-editor"; import { PagerData } from "./components/shared/pager/pager"; import { PanelPosition, PanelStateChangedArgs } from "./components/designer/panel/models"; @@ -69,6 +70,19 @@ export namespace Components { "selectedTabIndex"?: number; "tabs": Array; } + interface ElsaInputControlSwitch { + "codeEditorHeight": string; + "codeEditorSingleLineMode": boolean; + "context"?: IntellisenseContext; + "defaultSyntax": string; + "expression"?: string; + "fieldName"?: string; + "hint": string; + "isReadOnly"?: boolean; + "label": string; + "supportedSyntaxes": Array; + "syntax"?: string; + } interface ElsaInputTags { "fieldId"?: string; "placeHolder"?: string; @@ -93,7 +107,6 @@ export namespace Components { "monacoLibPath"?: string; "padding": string; "setJavaScriptLibs": (libs: Array) => Promise; - "setValue": (value: string) => Promise; "singleLineMode": boolean; "value": string; } @@ -240,6 +253,12 @@ declare global { prototype: HTMLElsaFormPanelElement; new (): HTMLElsaFormPanelElement; }; + interface HTMLElsaInputControlSwitchElement extends Components.ElsaInputControlSwitch, HTMLStencilElement { + } + var HTMLElsaInputControlSwitchElement: { + prototype: HTMLElsaInputControlSwitchElement; + new (): HTMLElsaInputControlSwitchElement; + }; interface HTMLElsaInputTagsElement extends Components.ElsaInputTags, HTMLStencilElement { } var HTMLElsaInputTagsElement: { @@ -394,6 +413,7 @@ declare global { "elsa-dropdown-input": HTMLElsaDropdownInputElement; "elsa-flowchart": HTMLElsaFlowchartElement; "elsa-form-panel": HTMLElsaFormPanelElement; + "elsa-input-control-switch": HTMLElsaInputControlSwitchElement; "elsa-input-tags": HTMLElsaInputTagsElement; "elsa-input-tags-dropdown": HTMLElsaInputTagsDropdownElement; "elsa-modal-dialog": HTMLElsaModalDialogElement; @@ -466,6 +486,21 @@ declare namespace LocalJSX { "selectedTabIndex"?: number; "tabs"?: Array; } + interface ElsaInputControlSwitch { + "codeEditorHeight"?: string; + "codeEditorSingleLineMode"?: boolean; + "context"?: IntellisenseContext; + "defaultSyntax"?: string; + "expression"?: string; + "fieldName"?: string; + "hint"?: string; + "isReadOnly"?: boolean; + "label"?: string; + "onExpressionChanged"?: (event: CustomEvent) => void; + "onSyntaxChanged"?: (event: CustomEvent) => void; + "supportedSyntaxes"?: Array; + "syntax"?: string; + } interface ElsaInputTags { "fieldId"?: string; "onValueChanged"?: (event: CustomEvent>) => void; @@ -596,6 +631,7 @@ declare namespace LocalJSX { "elsa-dropdown-input": ElsaDropdownInput; "elsa-flowchart": ElsaFlowchart; "elsa-form-panel": ElsaFormPanel; + "elsa-input-control-switch": ElsaInputControlSwitch; "elsa-input-tags": ElsaInputTags; "elsa-input-tags-dropdown": ElsaInputTagsDropdown; "elsa-modal-dialog": ElsaModalDialog; @@ -635,6 +671,7 @@ declare module "@stencil/core" { "elsa-dropdown-input": LocalJSX.ElsaDropdownInput & JSXBase.HTMLAttributes; "elsa-flowchart": LocalJSX.ElsaFlowchart & JSXBase.HTMLAttributes; "elsa-form-panel": LocalJSX.ElsaFormPanel & JSXBase.HTMLAttributes; + "elsa-input-control-switch": LocalJSX.ElsaInputControlSwitch & JSXBase.HTMLAttributes; "elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes; "elsa-input-tags-dropdown": LocalJSX.ElsaInputTagsDropdown & JSXBase.HTMLAttributes; "elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes; diff --git a/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx b/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx index 57f8ca4b8..93c26eeaa 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/activity-properties-editor/activity-properties-editor.tsx @@ -127,7 +127,7 @@ export class ActivityPropertiesEditor { type: inputDescriptor.type, expression: { type: syntax, - value: propertyValue + value: propertyValue // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax. } }; @@ -146,7 +146,10 @@ export class ActivityPropertiesEditor { {properties.filter(x => !!x.inputControl).map(propertyContext => { - return propertyContext.inputControl; + const key = `${node.id}-${propertyContext.inputContext.inputDescriptor.name}`; + return
+ {propertyContext.inputControl} +
; })} }; diff --git a/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/input-control-switch.tsx b/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/input-control-switch.tsx new file mode 100644 index 000000000..b2256f360 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/input-control-switch.tsx @@ -0,0 +1,194 @@ +import {Component, Event, EventEmitter, Listen, h, Prop, State} from '@stencil/core'; +import {IntellisenseContext, SyntaxNames} from "../../../models"; +import {enter, leave, toggle} from 'el-transition' +import {SyntaxSelectorIcon} from "../../icons/tooling/syntax-selector"; +import {MonacoValueChangedArgs} from "../../shared/monaco-editor/monaco-editor"; +import {Hint} from "../../shared/forms/hint"; + +export interface ExpressionChangedArs { + expression: string; + syntax: string; +} + +@Component({ + tag: 'elsa-input-control-switch', + shadow: false, +}) +export class InputControlSwitch { + private contextMenu: HTMLElement; + private monacoEditor: HTMLElsaMonacoEditorElement; + private defaultSyntaxValue: string; + private contextMenuWidget: HTMLElement; + + @Prop() label: string; + @Prop() hint: string; + @Prop() fieldName?: string; + @Prop() syntax?: string; + @Prop() expression?: string; + @Prop() defaultSyntax: string = SyntaxNames.Literal; + @Prop() supportedSyntaxes: Array = []; + @Prop() isReadOnly?: boolean; + @Prop() codeEditorHeight: string = '10em'; + @Prop() codeEditorSingleLineMode: boolean = false; + @Prop() context?: IntellisenseContext; + + @Event() syntaxChanged: EventEmitter; + @Event() expressionChanged: EventEmitter; + + @State() currentExpression?: string; + + @Listen('click', {target: 'window'}) + private onWindowClicked(event: Event) { + const target = event.target as HTMLElement; + + if (!this.contextMenuWidget || !this.contextMenuWidget.contains(target)) + this.closeContextMenu(); + } + + public componentWillLoad() { + this.currentExpression = this.expression; + } + + public render() { + return
+
+
+
+ {this.renderLabel()} +
+ {this.renderContextMenuWidget()} +
+
+ {this.renderEditor()} +
+ } + + private renderLabel() { + if (!this.label) + return undefined; + + const fieldId = this.fieldName; + const fieldLabel = this.label || fieldId; + + return ; + } + + private renderContextMenuWidget() { + if (this.supportedSyntaxes.length == 0) + return undefined; + + const selectedSyntax = this.syntax; + const advancedButtonClass = selectedSyntax ? 'text-blue-500' : 'text-gray-300' + + return
this.contextMenuWidget = el}> + +
+
this.contextMenu = el} + data-transition-enter="transition ease-out duration-100" + data-transition-enter-start="transform opacity-0 scale-95" + data-transition-enter-end="transform opacity-100 scale-100" + data-transition-leave="transition ease-in duration-75" + data-transition-leave-start="transform opacity-100 scale-100" + data-transition-leave-end="transform opacity-0 scale-95" + class="hidden origin-top-right absolute right-1 mt-1 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none z-10" role="menu" + aria-orientation="vertical" + aria-labelledby="options-menu"> + + +
+
+
; + } + + private renderContextMenuButton = () => this.syntax ? {this.syntax} : ; + + private renderEditor = () => { + const selectedSyntax = this.syntax; + const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax); + const value = this.expression; + const expressionEditorClass = selectedSyntax ? 'block' : 'hidden'; + const defaultEditorClass = selectedSyntax ? 'hidden' : 'block'; + + return ( +
+
+ this.onExpressionChanged(e.detail)} + ref={el => this.monacoEditor = el}/> +
+
+ +
+ +
+ ); + } + + private mapSyntaxToLanguage = (syntax: string): string => { + switch (syntax) { + case 'Json': + return 'json'; + case 'JavaScript': + return 'javascript'; + case 'Liquid': + return 'handlebars'; + case 'Literal': + default: + return 'plaintext'; + } + }; + + private toggleContextMenu() { + toggle(this.contextMenu); + } + + private openContextMenu() { + enter(this.contextMenu); + } + + private closeContextMenu() { + if (!!this.contextMenu) + leave(this.contextMenu); + } + + private selectDefaultEditor(e: Event) { + e.preventDefault(); + this.syntax = undefined; + this.closeContextMenu(); + } + + private async selectSyntax(e: Event, syntax: string) { + e.preventDefault(); + + this.syntax = syntax; + this.syntaxChanged.emit(syntax); + this.expressionChanged.emit({expression: this.currentExpression, syntax}); + + this.closeContextMenu(); + } + + private onSettingsClick(e: Event) { + this.toggleContextMenu(); + } + + private onExpressionChanged(e: MonacoValueChangedArgs) { + const expression = e.value; + this.currentExpression = expression; + this.expressionChanged.emit({expression, syntax: this.syntax}); + } +} diff --git a/src/designer/elsa-workflows-designer/src/components/icons/tooling/syntax-selector.tsx b/src/designer/elsa-workflows-designer/src/components/icons/tooling/syntax-selector.tsx new file mode 100644 index 000000000..93aa6af82 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/icons/tooling/syntax-selector.tsx @@ -0,0 +1,10 @@ +import {FunctionalComponent, h} from "@stencil/core"; + +export const SyntaxSelectorIcon: FunctionalComponent = () => + + + + + + + ; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/single-line.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/single-line.tsx index 97e38957a..4de6150d3 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/single-line.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/single-line.tsx @@ -1,27 +1,41 @@ -import {Component, Prop, h} from '@stencil/core'; +import {Component, h, Prop} from '@stencil/core'; import {LiteralExpression, SyntaxNames} from "../../models"; import {NodeInputContext} from "../../services/node-input-driver"; import {getInputPropertyValue} from "../../utils"; +import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; @Component({ tag: 'elsa-single-line-input', shadow: false }) export class SingleLineInput { - @Prop() inputContext: NodeInputContext; + @Prop() public inputContext: NodeInputContext; public render() { const inputContext = this.inputContext; - const inputProperty = inputContext.inputDescriptor; - const fieldName = inputProperty.name; - const fieldId = inputProperty.name; + const inputDescriptor = inputContext.inputDescriptor; + const fieldName = inputDescriptor.name; + const fieldId = inputDescriptor.name; + const displayName = inputDescriptor.displayName; + const hint = inputDescriptor.description; const input = getInputPropertyValue(inputContext); - const value = (input?.expression as LiteralExpression)?.value; - return + const value = (input?.expression as LiteralExpression)?.value; // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax. + const syntax = input?.expression?.type ?? inputDescriptor.defaultSyntax; + const supportedSyntaxes = ['Literal', 'JavaScript', 'Liquid']; + + return ( + + + + ); } private onPropertyEditorChanged = (e: Event) => { const inputElement = e.target as HTMLInputElement; this.inputContext.inputChanged(inputElement.value, SyntaxNames.Literal); } + + private onExpressionChanged = (e: CustomEvent) => { + this.inputContext.inputChanged(e.detail.expression, e.detail.syntax); + } } diff --git a/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/monaco-editor.tsx b/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/monaco-editor.tsx index 1c5ad5273..d3fca6821 100644 --- a/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/monaco-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/monaco-editor.tsx @@ -39,45 +39,45 @@ export class MonacoEditor { this.monaco.editor.setModelLanguage(model, this.language); } - @Method() - async setValue(value: string) { + @Watch('value') + valueChangeHandler(newValue: string) { if (!this.editor) return; const model = this.editor.getModel(); - model.setValue(value || ''); + model.setValue(newValue || ''); } @Method() async setJavaScriptLibs(libs: Array) { const monaco = this.monaco; + const editor = this.editor; const defaultLib: MonacoLib = { content: "", filePath: "lib.d.ts" }; - libs = [defaultLib, ...libs]; + libs = [...libs]; monaco.languages.typescript.javascriptDefaults.setExtraLibs(libs); for (const lib of libs) { - const oldModel = monaco.editor.getModel(lib.filePath); + + const oldModel = editor.getModel(lib.filePath); if (oldModel) oldModel.dispose(); - monaco.editor.createModel(lib.content, 'typescript', monaco.Uri.parse(lib.filePath)); + const newModel = monaco.editor.createModel(lib.content, 'typescript', monaco.Uri.parse(lib.filePath)); } } - async componentWillLoad() { + public async componentWillLoad() { const monacoLibPath = this.monacoLibPath || globalState.monacoLibPath; this.monaco = await initializeMonacoWorker(monacoLibPath); - await this.setJavaScriptLibs([]); - this.registerLiquid(); } - componentDidLoad() { + public async componentDidLoad() { const monaco = this.monaco; const language = this.language; @@ -135,6 +135,9 @@ export class MonacoEditor { this.editor = monaco.editor.create(this.container, options); + await this.setJavaScriptLibs([]); + this.registerLiquid(); + this.editor.onDidChangeModelContent(e => { const value = this.editor.getValue(); const markers = monaco.editor.getModelMarkers({owner: language}); @@ -207,7 +210,7 @@ export class MonacoEditor { } render() { - const padding = this.padding || 'elsa-pt-1.5 elsa-pl-1'; + const padding = this.padding || 'pt-1.5 pl-1'; return ( - {inputControl(context)} - ); + return inputControl(context); } supportsInput(context: NodeInputContext): boolean { diff --git a/src/designer/elsa-workflows-designer/src/models/api.ts b/src/designer/elsa-workflows-designer/src/models/api.ts index 202288342..005385d69 100644 --- a/src/designer/elsa-workflows-designer/src/models/api.ts +++ b/src/designer/elsa-workflows-designer/src/models/api.ts @@ -121,3 +121,8 @@ export interface VersionOptions { allVersions?: boolean; version?: number; } + +export interface IntellisenseContext { + activityTypeName: string; + propertyName: string; +} diff --git a/src/designer/elsa-workflows-designer/src/models/expressions.ts b/src/designer/elsa-workflows-designer/src/models/expressions.ts index 71fd39011..ac0bea6b5 100644 --- a/src/designer/elsa-workflows-designer/src/models/expressions.ts +++ b/src/designer/elsa-workflows-designer/src/models/expressions.ts @@ -8,7 +8,7 @@ export interface LiteralExpression { export interface JavaScriptExpression { type: ExpressionType; - scriptExpression: string; + value: string; } export interface JsonExpression { diff --git a/src/samples/aspnet/Elsa.Samples.Web1/Program.cs b/src/samples/aspnet/Elsa.Samples.Web1/Program.cs index 2295e7b9d..89a9dbf11 100644 --- a/src/samples/aspnet/Elsa.Samples.Web1/Program.cs +++ b/src/samples/aspnet/Elsa.Samples.Web1/Program.cs @@ -15,6 +15,7 @@ using Elsa.Pipelines.WorkflowExecution.Components; using Elsa.Runtime.ProtoActor.Extensions; using Elsa.Samples.Web1.Activities; using Elsa.Samples.Web1.Workflows; +using Elsa.Scripting.JavaScript; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -27,7 +28,6 @@ var services = builder.Services; services .AddElsa() .AddMediator() - //.AddInMemoryPersistence() .AddEntityFrameworkCorePersistence((_, ef) => ef.UseSqlite()) .IndexWorkflowTriggers() .AddElsaManagement() @@ -57,6 +57,9 @@ services services .AddTrigger(); +// Register scripting languages. +services.AddJavaScriptExpressions(); + // Configure middleware pipeline. var app = builder.Build(); var serviceProvider = app.Services; diff --git a/src/scripting/Elsa.Scripting.JavaScript/Elsa.Scripting.JavaScript.csproj b/src/scripting/Elsa.Scripting.JavaScript/Elsa.Scripting.JavaScript.csproj index 39ea45dfb..ae0c133f5 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Elsa.Scripting.JavaScript.csproj +++ b/src/scripting/Elsa.Scripting.JavaScript/Elsa.Scripting.JavaScript.csproj @@ -11,6 +11,7 @@ + diff --git a/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpression.cs b/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpression.cs index 3dfb386a3..0b56fc890 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpression.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpression.cs @@ -7,12 +7,12 @@ namespace Elsa.Scripting.JavaScript; public class JavaScriptExpression : IExpression { - public JavaScriptExpression(string scriptExpression) + public JavaScriptExpression(string value) { - ScriptExpression = scriptExpression; + Value = value; } - public string ScriptExpression { get; } + public string Value { get; } } public class JavaScriptExpressionHandler : IExpressionHandler @@ -22,6 +22,6 @@ public class JavaScriptExpressionHandler : IExpressionHandler var javaScriptExpression = (JavaScriptExpression)expression; // TODO: evaluate expression. - return new ValueTask(javaScriptExpression.ScriptExpression); + return new ValueTask(javaScriptExpression.Value); } } \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpressionSyntaxProvider.cs b/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpressionSyntaxProvider.cs new file mode 100644 index 000000000..889e97924 --- /dev/null +++ b/src/scripting/Elsa.Scripting.JavaScript/JavaScriptExpressionSyntaxProvider.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Contracts; +using Elsa.Expressions; +using Elsa.Management.Contracts; +using Elsa.Management.Models; +using Elsa.Models; + +namespace Elsa.Scripting.JavaScript; + +public class JavaScriptExpressionSyntaxProvider : IExpressionSyntaxProvider +{ + private readonly IIdentityGenerator _identityGenerator; + + public JavaScriptExpressionSyntaxProvider(IIdentityGenerator identityGenerator) + { + _identityGenerator = identityGenerator; + } + + public ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) + { + var javaScript = CreateJavaScriptDescriptor(); + + return ValueTask.FromResult>(new[] { javaScript }); + } + + private ExpressionSyntaxDescriptor CreateJavaScriptDescriptor() => CreateDescriptor( + "JavaScript", + CreateJavaScriptExpression, + context => new JavaScriptExpressionReference(context.GetExpression()), + expression => expression.Value); + + private ExpressionSyntaxDescriptor CreateDescriptor( + string syntax, + Func constructor, + Func createLocationReference, + Func expressionValue) => + new() + { + Syntax = syntax, + Type = typeof(TExpression), + CreateExpression = constructor, + CreateLocationReference = context => + { + var reference = createLocationReference(context); + + if (string.IsNullOrWhiteSpace(reference.Id)) + reference.Id = GenerateId(); + + return reference; + }, + CreateSerializableObject = context => new + { + Type = syntax, + Value = expressionValue(context.GetExpression()) + } + }; + + private IExpression CreateJavaScriptExpression(ExpressionConstructorContext context) + { + var script = context.Element.GetProperty("value").ToString(); + return new JavaScriptExpression(script); + } + + private string GenerateId() => _identityGenerator.GenerateId(); +} \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/ServiceCollectionExtensions.cs b/src/scripting/Elsa.Scripting.JavaScript/ServiceCollectionExtensions.cs index da0f84e30..3fa4e5d29 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/ServiceCollectionExtensions.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Elsa.Extensions; +using Elsa.Management.Contracts; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Scripting.JavaScript; @@ -7,6 +8,8 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddJavaScriptExpressions(this IServiceCollection services) { - return services.AddExpressionHandler(); + return services + .AddSingleton() + .AddExpressionHandler(); } } \ No newline at end of file