diff --git a/src/designer/elsa-workflows-designer/package-lock.json b/src/designer/elsa-workflows-designer/package-lock.json index 65cabca03..03e509f92 100644 --- a/src/designer/elsa-workflows-designer/package-lock.json +++ b/src/designer/elsa-workflows-designer/package-lock.json @@ -1,12 +1,12 @@ { "name": "@elsa-workflows/elsa-workflows-designer", - "version": "3.0.2-preview.36", + "version": "3.0.2-preview.40", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@elsa-workflows/elsa-workflows-designer", - "version": "3.0.2-preview.2", + "version": "3.0.2-preview.40", "license": "MIT", "dependencies": { "@antv/layout": "^0.3.11", @@ -15,6 +15,7 @@ "@stencil/core": "^2.20.0", "@stencil/state-tunnel": "^1.0.1", "@stencil/store": "^2.0.1", + "async-mutex": "^0.4.0", "axios": "^0.27.2", "axios-middleware": "^0.3.1", "el-transition": "^0.0.7", @@ -392,6 +393,14 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, + "node_modules/async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1614,6 +1623,11 @@ "node": ">=8.0" } }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, "node_modules/typedi": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", @@ -1980,6 +1994,14 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, + "async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "requires": { + "tslib": "^2.4.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2850,6 +2872,11 @@ "is-number": "^7.0.0" } }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, "typedi": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/typedi/-/typedi-0.10.0.tgz", diff --git a/src/designer/elsa-workflows-designer/package.json b/src/designer/elsa-workflows-designer/package.json index 65e932a3d..35afd029b 100644 --- a/src/designer/elsa-workflows-designer/package.json +++ b/src/designer/elsa-workflows-designer/package.json @@ -32,6 +32,7 @@ "@stencil/core": "^2.20.0", "@stencil/state-tunnel": "^1.0.1", "@stencil/store": "^2.0.1", + "async-mutex": "^0.4.0", "axios": "^0.27.2", "axios-middleware": "^0.3.1", "el-transition": "^0.0.7", diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index 0d087dddb..9187c1ac1 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -8,14 +8,14 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { ActivityDefinition, ActivityDefinitionPropsUpdatedArgs, ActivityDefinitionSummary, ActivityDefinitionUpdatedArgs } from "./modules/activity-definitions/models"; import { ActivityDriverRegistry } from "./services"; import { Activity, ActivityDeletedArgs, ActivitySelectedArgs, ChildActivitySelectedArgs, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Variable, WorkflowInstance, WorkflowInstanceSummary, WorkflowUpdatedArgs } from "./models"; -import { WorkflowDefinition, WorkflowDefinitionSummary } from "./modules/workflow-definitions/models/entities"; import { ActivityUpdatedArgs, DeleteActivityRequestedArgs, Widget, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs } from "./modules/workflow-definitions/models/ui"; import { PublishClickedArgs } from "./modules/activity-definitions/components/publish-button"; import { NotificationType } from "./modules/notifications/models"; import { Button } from "./components/shared/button-group/models"; -import { ActivityInputContext } from "./services/node-input-driver"; +import { ActivityInputContext } from "./services/activity-input-driver"; import { ContextMenuAnchorPoint, ContextMenuItem } from "./components/shared/context-menu/models"; import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models"; +import { WorkflowDefinition, WorkflowDefinitionSummary } from "./modules/workflow-definitions/models/entities"; import { Graph } from "@antv/x6"; import { AddActivityArgs, FlowchartPathItem, LayoutDirection, RenameActivityArgs, UpdateActivityArgs } from "./modules/flowchart/models"; import { OutNode } from "@antv/layout"; @@ -25,7 +25,7 @@ import { ExpressionChangedArs } from "./components/designer/input-control-switch import { SignedInArgs } from "./modules/login/models"; import { ModalActionClickArgs, ModalActionDefinition, ModalDialogInstance } from "./components/shared/modal-dialog/models"; import { ModalType } from "./components/shared/modal-dialog/modal-type"; -import { MonacoLib, MonacoValueChangedArgs } from "./components/shared/monaco-editor/monaco-editor"; +import { MonacoValueChangedArgs } from "./components/shared/monaco-editor/monaco-editor"; import { PagerData } from "./components/shared/pager/pager"; import { PanelPosition, PanelStateChangedArgs } from "./components/panel/models"; import { PublishClickedArgs as PublishClickedArgs1 } from "./modules/workflow-definitions/components/publish-button"; @@ -54,10 +54,11 @@ export namespace Components { } interface ElsaActivityPropertiesEditor { "activity"?: Activity; + "containerId": string; + "containerType": string; "hide": () => Promise; "show": () => Promise; "variables": Array; - "workflowDefinition": WorkflowDefinition; } interface ElsaActivityPublishButton { "publishing": boolean; @@ -135,8 +136,11 @@ export namespace Components { interface ElsaHomePage { } interface ElsaInputControlSwitch { + "activityType": string; "codeEditorHeight": string; "codeEditorSingleLineMode": boolean; + "containerId": string; + "containerType": string; "context"?: IntellisenseContext; "defaultSyntax": string; "expression"?: string; @@ -145,6 +149,7 @@ export namespace Components { "hint": string; "isReadOnly"?: boolean; "label": string; + "propertyName": string; "supportedSyntaxes": Array; "syntax"?: string; } @@ -180,11 +185,11 @@ export namespace Components { interface ElsaModalDialogContainer { } interface ElsaMonacoEditor { + "addJavaScriptLib": (libSource: string, libUri: string) => Promise; "editorHeight": string; "language": string; - "monacoLibPath"?: string; "padding": string; - "setJavaScriptLibs": (libs: Array) => Promise; + "setValue": (value: string) => Promise; "singleLineMode": boolean; "value": string; } @@ -879,10 +884,11 @@ declare namespace LocalJSX { } interface ElsaActivityPropertiesEditor { "activity"?: Activity; + "containerId"?: string; + "containerType"?: string; "onActivityUpdated"?: (event: ElsaActivityPropertiesEditorCustomEvent) => void; "onDeleteActivityRequested"?: (event: ElsaActivityPropertiesEditorCustomEvent) => void; "variables"?: Array; - "workflowDefinition"?: WorkflowDefinition; } interface ElsaActivityPublishButton { "onExportClicked"?: (event: ElsaActivityPublishButtonCustomEvent) => void; @@ -962,8 +968,11 @@ declare namespace LocalJSX { interface ElsaHomePage { } interface ElsaInputControlSwitch { + "activityType"?: string; "codeEditorHeight"?: string; "codeEditorSingleLineMode"?: boolean; + "containerId"?: string; + "containerType"?: string; "context"?: IntellisenseContext; "defaultSyntax"?: string; "expression"?: string; @@ -974,6 +983,7 @@ declare namespace LocalJSX { "label"?: string; "onExpressionChanged"?: (event: ElsaInputControlSwitchCustomEvent) => void; "onSyntaxChanged"?: (event: ElsaInputControlSwitchCustomEvent) => void; + "propertyName"?: string; "supportedSyntaxes"?: Array; "syntax"?: string; } @@ -1016,7 +1026,6 @@ declare namespace LocalJSX { interface ElsaMonacoEditor { "editorHeight"?: string; "language"?: string; - "monacoLibPath"?: string; "onValueChanged"?: (event: ElsaMonacoEditorCustomEvent) => void; "padding"?: string; "singleLineMode"?: boolean; 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 index b4ae9f817..c40ec5563 100644 --- 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 @@ -7,6 +7,9 @@ import {MonacoValueChangedArgs} from "../../shared/monaco-editor/monaco-editor"; import {Hint} from "../../shared/forms/hint"; import {mapSyntaxToLanguage} from "../../../utils"; import descriptorsStore from "../../../data/descriptors-store"; +import {Container} from "typedi"; +import {ElsaClient, ElsaClientProvider} from "../../../services/api-client/elsa-client"; +import InputControlSwitchContextState from "./state"; export interface ExpressionChangedArs { expression: string; @@ -27,6 +30,12 @@ export class InputControlSwitch { this.onExpressionChangedDebounced = debounce(this.onExpressionChanged, 10); } + // Tunneled props. + @Prop() containerType: string; + @Prop() containerId: string; + @Prop() activityType: string; + @Prop() propertyName: string; + @Prop() label: string; @Prop() hideLabel: boolean; @Prop() hint: string; @@ -53,10 +62,21 @@ export class InputControlSwitch { this.closeContextMenu(); } - componentWillLoad() { + async componentWillLoad() { this.currentExpression = this.expression; } + async componentDidLoad() { + const elsaClient = await Container.get(ElsaClientProvider).getElsaClient(); + const containerType = this.containerType; + const containerId = this.containerId; + const activityTypeName = this.activityType; + const propertyName = this.propertyName; + const typeDefinitions = await elsaClient.scripting.javaScriptApi.getTypeDefinitions({containerType, containerId, activityTypeName, propertyName}); + const libUri = 'defaultLib:lib.es6.d.ts'; + await this.monacoEditor.addJavaScriptLib(typeDefinitions, libUri); + } + render() { if (this.hideLabel && !this.shouldRenderMonaco()) { @@ -200,3 +220,5 @@ export class InputControlSwitch { this.expressionChanged.emit({expression, syntax: this.syntax}); } } + +InputControlSwitchContextState.injectProps(InputControlSwitch, ['containerType', 'containerId', 'activityType', 'propertyName']) diff --git a/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/state.tsx b/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/state.tsx new file mode 100644 index 000000000..6461a13c8 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/components/designer/input-control-switch/state.tsx @@ -0,0 +1,19 @@ +import {h} from "@stencil/core"; +import {createProviderConsumer} from "@stencil/state-tunnel"; + +export interface InputControlSwitchContextState { + containerType: string; + containerId: string; + activityType: string; + propertyName: string; +} + +export default createProviderConsumer( + { + containerType: null, + containerId: null, + activityType: null, + propertyName: null + }, + (subscribe, child) => () +); diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx index 593ecfa7d..28b233692 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/check-list.tsx @@ -1,7 +1,7 @@ import {Component, h, Prop, State} from '@stencil/core'; import {uniq} from 'lodash' import {JsonExpression, LiteralExpression, SelectList, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getSelectListItems, getInputPropertyValue, parseJson} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/checkbox.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/checkbox.tsx index 1f28f064d..cbfa1ce90 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/checkbox.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/checkbox.tsx @@ -1,6 +1,6 @@ import {Component, Prop, h, State} from '@stencil/core'; import {LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue } from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/code-editor.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/code-editor.tsx index 28e2c5415..bf6dd8d1d 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/code-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/code-editor.tsx @@ -1,6 +1,6 @@ import {Component, h, Prop} from '@stencil/core'; import {EditorHeight, LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue} from "../../utils"; import {MonacoValueChangedArgs} from "../shared/monaco-editor/monaco-editor"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/dropdown.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/dropdown.tsx index 2a4476b14..c6ca55961 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/dropdown.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/dropdown.tsx @@ -1,6 +1,6 @@ import {Component, Prop, h} from '@stencil/core'; import {LiteralExpression, SelectList, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue, getSelectListItems} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/multi-line.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/multi-line.tsx index 07935c7db..2557c606b 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/multi-line.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/multi-line.tsx @@ -1,6 +1,6 @@ import {Component, Prop, h} from '@stencil/core'; import {LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx index dd969e568..fdbf7c523 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/multi-text.tsx @@ -1,6 +1,6 @@ import {Component, Prop, h} from '@stencil/core'; import {JsonExpression, LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue, parseJson} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/radio-list.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/radio-list.tsx index f60b7b122..946d7adb1 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/radio-list.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/radio-list.tsx @@ -1,6 +1,6 @@ import {Component, h, Prop, State} from '@stencil/core'; import {LiteralExpression, SelectList, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getSelectListItems, getInputPropertyValue} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; 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 9e78101a2..b9085fb9c 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,6 +1,6 @@ import {Component, h, Prop} from '@stencil/core'; import {LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue} from "../../utils"; import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch"; import descriptorsStore from "../../data/descriptors-store"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/type-picker.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/type-picker.tsx index 9ef1e1cac..1d962d810 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/type-picker.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/type-picker.tsx @@ -1,7 +1,7 @@ import {Component, Prop, h} from '@stencil/core'; import {groupBy} from 'lodash'; import {LiteralExpression, SyntaxNames} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getInputPropertyValue} from "../../utils"; import descriptorsStore from '../../data/descriptors-store'; import {VariableDescriptor} from "../../services/api-client/variable-descriptors-api"; diff --git a/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx b/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx index 0ca9ec8f2..e6b5ceae6 100644 --- a/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx +++ b/src/designer/elsa-workflows-designer/src/components/inputs/variable-picker.tsx @@ -1,9 +1,9 @@ import {Component, Prop, h} from '@stencil/core'; import {SyntaxNames, Variable} from "../../models"; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {getPropertyValue} from "../../utils"; import {FormEntry} from "../shared/forms/form-entry"; -import WorkflowDefinitionTunnel from "../../state/workflow-definition-state"; +import WorkflowDefinitionTunnel from "../../modules/workflow-definitions/state"; @Component({ tag: 'elsa-variable-picker-input', 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 1e04cd4ba..89aacfb7f 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 @@ -1,5 +1,5 @@ -import {Component, Event, EventEmitter, h, Host, Method, Prop, Watch} from '@stencil/core'; -import {initializeMonacoWorker, Monaco} from "./utils"; +import { Component, Event, EventEmitter, h, Host, Method, Prop, Watch } from '@stencil/core'; +import { initializeMonacoWorker, Monaco, EditorVariables } from "./utils"; import monacoStore from '../../../data/monaco-store'; export interface MonacoValueChangedArgs { @@ -7,28 +7,23 @@ export interface MonacoValueChangedArgs { markers: Array; } -export interface MonacoLib { - content: string; - filePath?: string; -} - @Component({ tag: 'elsa-monaco-editor', styleUrl: 'monaco-editor.scss', shadow: false }) -export class MonacoEditor { +export class ElsaMonaco { private monaco: Monaco; - private container: HTMLElement; - private editor: any; - @Prop() public monacoLibPath?: string; - @Prop() public editorHeight: string = '10em'; - @Prop() public value: string; - @Prop() public language: string; - @Prop() public singleLineMode: boolean = false; - @Prop() public padding: string; - @Event() public valueChanged: EventEmitter; + @Prop({ attribute: 'editor-height', reflect: true }) editorHeight: string = '5em'; + @Prop() value: string; + @Prop() language: string; + @Prop({ attribute: 'single-line', reflect: true }) singleLineMode: boolean = false; + @Prop() padding: string; + @Event({ eventName: 'valueChanged' }) valueChanged: EventEmitter; + + container: HTMLElement; + editor: any; @Watch('language') languageChangeHandler(newValue: string) { @@ -40,35 +35,46 @@ export class MonacoEditor { } @Method() - async setJavaScriptLibs(libs: Array) { + async setValue(value: string) { + if (!this.editor) + return; + + const model = this.editor.getModel(); + model.setValue(value || ''); + } + + @Method() + async addJavaScriptLib(libSource: string, libUri: string) { const monaco = this.monaco; - const editor = this.editor; - const defaultLib: MonacoLib = { - content: "", - filePath: "lib.d.ts" - }; + monaco.languages.typescript.javascriptDefaults.setExtraLibs([{ + filePath: "lib.es5.d.ts" + }, { + content: libSource, + filePath: libUri + }]); - libs = [...libs]; - monaco.languages.typescript.javascriptDefaults.setExtraLibs(libs); + const oldModel = monaco.editor.getModel(libUri); - for (const lib of libs) { + if (oldModel) + oldModel.dispose(); - const oldModel = editor.getModel(lib.filePath); + const newModel = monaco.editor.createModel(libSource, 'typescript', monaco.Uri.parse(libUri)); + const matches = libSource.matchAll(/declare const (\w+): (string|number)/g); - if (oldModel) - oldModel.dispose(); + EditorVariables.splice(0, EditorVariables.length); - const newModel = monaco.editor.createModel(lib.content, 'typescript', monaco.Uri.parse(lib.filePath)); + for(const match of matches) { + EditorVariables.push({variableName: match[1], type: match[2] }); } } - public async componentWillLoad() { - const monacoLibPath = this.monacoLibPath || monacoStore.monacoLibPath; + async componentWillLoad() { + const monacoLibPath = monacoStore.monacoLibPath; this.monaco = await initializeMonacoWorker(monacoLibPath); } - public async componentDidLoad() { + componentDidLoad() { const monaco = this.monaco; const language = this.language; @@ -118,21 +124,18 @@ export class MonacoEditor { lineNumbersMinChars: 0, folding: false, scrollBeyondLastColumn: 0, - scrollbar: {horizontal: 'hidden', vertical: 'hidden'}, - find: {addExtraSpaceOnTop: false, autoFindInSelection: 'never', seedSearchStringFromSelection: false}, + scrollbar: { horizontal: 'hidden', vertical: 'hidden' }, + find: { addExtraSpaceOnTop: false, autoFindInSelection: 'never', seedSearchStringFromSelection: false }, } } } 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}); - this.valueChanged.emit({value: value, markers: markers}); + const markers = monaco.editor.getModelMarkers({ owner: language }); + this.valueChanged.emit({ value: value, markers: markers }); }); if (this.singleLineMode) { @@ -166,47 +169,13 @@ export class MonacoEditor { editor.dispose(); } - registerLiquid() { - const monaco = this.monaco; - monaco.languages.register({id: 'liquid'}); - - monaco.languages.registerCompletionItemProvider('liquid', { - provideCompletionItems: () => { - const autocompleteProviderItems = []; - const keywords = ['assign', 'capture', 'endcapture', 'increment', 'decrement', - 'if', 'else', 'elsif', 'endif', 'for', 'endfor', 'break', - 'continue', 'limit', 'offset', 'range', 'reversed', 'cols', - 'case', 'endcase', 'when', 'block', 'endblock', 'true', 'false', - 'in', 'unless', 'endunless', 'cycle', 'tablerow', 'endtablerow', - 'contains', 'startswith', 'endswith', 'comment', 'endcomment', - 'raw', 'endraw', 'editable', 'endentitylist', 'endentityview', 'endinclude', - 'endmarker', 'entitylist', 'entityview', 'forloop', 'image', 'include', - 'marker', 'outputcache', 'plugin', 'style', 'text', 'widget', - 'abs', 'append', 'at_least', 'at_most', 'capitalize', 'ceil', 'compact', - 'concat', 'date', 'default', 'divided_by', 'downcase', 'escape', - 'escape_once', 'first', 'floor', 'join', 'last', 'lstrip', 'map', - 'minus', 'modulo', 'newline_to_br', 'plus', 'prepend', 'remove', - 'remove_first', 'replace', 'replace_first', 'reverse', 'round', - 'rstrip', 'size', 'slice', 'sort', 'sort_natural', 'split', 'strip', - 'strip_html', 'strip_newlines', 'times', 'truncate', 'truncatewords', - 'uniq', 'upcase', 'url_decode', 'url_encode']; - - for (let i = 0; i < keywords.length; i++) { - autocompleteProviderItems.push({'label': keywords[i], kind: monaco.languages.CompletionItemKind.Keyword}); - } - - return {suggestions: autocompleteProviderItems}; - } - }); - } - render() { const padding = this.padding || 'pt-1.5 pl-1'; return ( -
this.container = el} class={`monaco-editor-container ${padding}`}/> + style={{ 'min-height': this.editorHeight }}> +
this.container = el} class={`monaco-editor-container ${padding}`} /> ) } diff --git a/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/utils.ts b/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/utils.ts index cbea55830..0f271ffb9 100644 --- a/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/utils.ts +++ b/src/designer/elsa-workflows-designer/src/components/shared/monaco-editor/utils.ts @@ -1,4 +1,5 @@ -const win = window as any; +import { Mutex } from "async-mutex"; +const win = window as any; const require = win.require; export interface Monaco { @@ -8,32 +9,102 @@ export interface Monaco { Uri: any; } -export function initializeMonacoWorker(libPath?: string): Promise { +export interface EditorVariable { + variableName: string; + type: string; +} - if (win.monaco) - return win.monaco; +export var EditorVariables: Array = []; - if (!libPath) - return win.monaco; +let isInitialized: boolean; +const mutex = new Mutex(); - const origin = document.location.origin; - const baseUrl = libPath.startsWith('http') ? libPath : `${origin}/${libPath}`; +export async function initializeMonacoWorker(libPath?: string): Promise { - require.config({paths: {'vs': `${baseUrl}/vs`}}); - win.MonacoEnvironment = {getWorkerUrl: () => proxy}; + return await mutex.runExclusive(async () => { - let proxy = URL.createObjectURL(new Blob([` + if (isInitialized) { + + return win.monaco; + } + + const origin = document.location.origin; + const baseUrl = libPath.startsWith('http') ? libPath : `${origin}/${libPath}`; + + require.config({ paths: { 'vs': `${baseUrl}/vs` } }); + win.MonacoEnvironment = { getWorkerUrl: () => proxy }; + + let proxy = URL.createObjectURL(new Blob([` self.MonacoEnvironment = { baseUrl: '${baseUrl}' }; importScripts('${baseUrl}/vs/base/worker/workerMain.js'); -`], {type: 'text/javascript'})); +`], { type: 'text/javascript' })); - return new Promise(resolve => { - require(["vs/editor/editor.main"], () => { - resolve(win.monaco); + return new Promise(resolve => { + require(["vs/editor/editor.main"], () => { + isInitialized = true; + registerLiquid(win.monaco); + registerSql(win.monaco); + resolve(win.monaco); + }); }); }); } -export const DefaultLibUri: string = 'defaultLib:lib.es6.d.ts'; +function registerLiquid(monaco: any) { + monaco.languages.register({ id: 'liquid' }); + + monaco.languages.registerCompletionItemProvider('liquid', { + provideCompletionItems: () => { + const autocompleteProviderItems = []; + const keywords = ['assign', 'capture', 'endcapture', 'increment', 'decrement', + 'if', 'else', 'elsif', 'endif', 'for', 'endfor', 'break', + 'continue', 'limit', 'offset', 'range', 'reversed', 'cols', + 'case', 'endcase', 'when', 'block', 'endblock', 'true', 'false', + 'in', 'unless', 'endunless', 'cycle', 'tablerow', 'endtablerow', + 'contains', 'startswith', 'endswith', 'comment', 'endcomment', + 'raw', 'endraw', 'editable', 'endentitylist', 'endentityview', 'endinclude', + 'endmarker', 'entitylist', 'entityview', 'forloop', 'image', 'include', + 'marker', 'outputcache', 'plugin', 'style', 'text', 'widget', + 'abs', 'append', 'at_least', 'at_most', 'capitalize', 'ceil', 'compact', + 'concat', 'date', 'default', 'divided_by', 'downcase', 'escape', + 'escape_once', 'first', 'floor', 'join', 'last', 'lstrip', 'map', + 'minus', 'modulo', 'newline_to_br', 'plus', 'prepend', 'remove', + 'remove_first', 'replace', 'replace_first', 'reverse', 'round', + 'rstrip', 'size', 'slice', 'sort', 'sort_natural', 'split', 'strip', + 'strip_html', 'strip_newlines', 'times', 'truncate', 'truncatewords', + 'uniq', 'upcase', 'url_decode', 'url_encode']; + + for (let i = 0; i < keywords.length; i++) { + autocompleteProviderItems.push({ 'label': keywords[i], kind: monaco.languages.CompletionItemKind.Keyword }); + } + + return { suggestions: autocompleteProviderItems }; + } + }); +} + +function registerSql(monaco: any) { + + monaco.languages.registerCompletionItemProvider('sql', { + triggerCharacters: ["@"], + provideCompletionItems: (model, position) => { + + const word = model.getWordUntilPosition(position) + + const autocompleteProviderItems = []; + for (const variable of EditorVariables) { + autocompleteProviderItems.push({ + label: `${variable.variableName}: ${variable.type}`, + kind: monaco.languages.CompletionItemKind.Variable, + insertText: variable.variableName + }); + } + + return { suggestions: autocompleteProviderItems }; + } + }); + +} + diff --git a/src/designer/elsa-workflows-designer/src/drivers/input/default-input-driver.tsx b/src/designer/elsa-workflows-designer/src/drivers/input/default-input-driver.tsx index b139bcbd8..4ca7c3d81 100644 --- a/src/designer/elsa-workflows-designer/src/drivers/input/default-input-driver.tsx +++ b/src/designer/elsa-workflows-designer/src/drivers/input/default-input-driver.tsx @@ -1,11 +1,11 @@ -import {NodeInputDriver, ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputDriver, ActivityInputContext} from "../../services/activity-input-driver"; import {Container} from "typedi"; import {InputControlRegistry} from "../../services/input-control-registry"; import {h} from "@stencil/core"; import {FormEntry} from "../../components/shared/forms/form-entry"; // A standard input driver that determines the UI to be displayed based on the UI hint of the activity input property. -export class DefaultInputDriver implements NodeInputDriver { +export class DefaultInputDriver implements ActivityInputDriver { private inputControlRegistry: InputControlRegistry; constructor() { diff --git a/src/designer/elsa-workflows-designer/src/index.ts b/src/designer/elsa-workflows-designer/src/index.ts index 81a65ac5b..598254b09 100644 --- a/src/designer/elsa-workflows-designer/src/index.ts +++ b/src/designer/elsa-workflows-designer/src/index.ts @@ -2,6 +2,6 @@ import 'reflect-metadata'; export {Components, JSX} from './components'; export {Container} from 'typedi'; -export {ElsaApiClientProvider} from './services/api-client/api-client'; +export {ElsaClientProvider} from './services/api-client/elsa-client'; export {stripActivityNameSpace} from './utils'; export {PluginRegistry, ActivityNameFormatter} from './services'; diff --git a/src/designer/elsa-workflows-designer/src/models/ui.ts b/src/designer/elsa-workflows-designer/src/models/ui.ts index 07f5922af..298be6dd5 100644 --- a/src/designer/elsa-workflows-designer/src/models/ui.ts +++ b/src/designer/elsa-workflows-designer/src/models/ui.ts @@ -1,4 +1,4 @@ -import {ActivityInputContext} from "../services/node-input-driver"; +import {ActivityInputContext} from "../services/activity-input-driver"; import {Activity, ActivityDescriptor} from "./core"; export interface TabDefinition { diff --git a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx index e190480a3..d64978b46 100644 --- a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx @@ -153,6 +153,8 @@ export class Editor { private renderSelectedObject = () => { if (!!this.selectedActivity) return this.onActivityUpdated(e)}/>; diff --git a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/services/api.ts b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/services/api.ts index 01c42fe68..bc115a756 100644 --- a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/services/api.ts +++ b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/services/api.ts @@ -1,5 +1,4 @@ import {Service} from "typedi"; -import {ElsaApiClientProvider} from "../../../services"; import { ActivityDefinition, ActivityDefinitionSummary, @@ -12,12 +11,13 @@ import { } from "../models"; import {getVersionOptionsString, serializeQueryString} from "../../../utils"; import {PagedList} from "../../../models"; +import {ElsaClientProvider} from "../../../services"; @Service() export class ActivityDefinitionsApi { - private provider: ElsaApiClientProvider; + private provider: ElsaClientProvider; - constructor(provider: ElsaApiClientProvider) { + constructor(provider: ElsaClientProvider) { this.provider = provider; } diff --git a/src/designer/elsa-workflows-designer/src/modules/flow-switch/flow-switch-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/flow-switch/flow-switch-editor.tsx index 86b7ea0e8..5d216f1ff 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flow-switch/flow-switch-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flow-switch/flow-switch-editor.tsx @@ -1,6 +1,6 @@ import {Component, h, Prop, State, Watch} from "@stencil/core"; import {camelCase} from 'lodash'; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {mapSyntaxToLanguage} from "../../utils"; import {SyntaxNames} from "../../models"; import {MonacoValueChangedArgs} from "../../components/shared/monaco-editor/monaco-editor"; @@ -28,7 +28,7 @@ export class FlowSwitchEditor { private updateCases(){ const inputContext = this.inputContext; - const activity = this.inputContext.node; + const activity = this.inputContext.activity; const inputDescriptor = inputContext.inputDescriptor; const propertyName = inputDescriptor.name; const camelCasePropertyName = camelCase(propertyName); @@ -67,7 +67,7 @@ export class FlowSwitchEditor { private updateActivity = () => { const inputContext = this.inputContext; - const activity = this.inputContext.node; + const activity = this.inputContext.activity; const inputDescriptor = inputContext.inputDescriptor; const propertyName = inputDescriptor.name; const camelCasePropertyName = camelCase(propertyName); diff --git a/src/designer/elsa-workflows-designer/src/modules/labels/labels-api.ts b/src/designer/elsa-workflows-designer/src/modules/labels/labels-api.ts index 8f416d4a5..d38b5997d 100644 --- a/src/designer/elsa-workflows-designer/src/modules/labels/labels-api.ts +++ b/src/designer/elsa-workflows-designer/src/modules/labels/labels-api.ts @@ -1,13 +1,13 @@ import {List} from "../../models"; import {Label} from "./models"; -import {ElsaApiClientProvider} from "../../services"; +import {ElsaClientProvider} from "../../services"; import {Service} from "typedi"; @Service() export class LabelsApi { - private provider: ElsaApiClientProvider; + private provider: ElsaClientProvider; - constructor(provider: ElsaApiClientProvider) { + constructor(provider: ElsaClientProvider) { this.provider = provider; } @@ -38,9 +38,9 @@ export class LabelsApi { @Service() export class WorkflowDefinitionLabelsApi { - private provider: ElsaApiClientProvider; + private provider: ElsaClientProvider; - constructor(provider: ElsaApiClientProvider) { + constructor(provider: ElsaClientProvider) { this.provider = provider; } diff --git a/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx b/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx index 1dc29a9e3..58621ca6b 100644 --- a/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/login/plugin.tsx @@ -3,7 +3,7 @@ import {h} from "@stencil/core"; import {Service as MiddlewareService} from 'axios-middleware'; import {EventTypes, Plugin} from "../../models"; import {Container, Service} from "typedi"; -import {StudioService, AuthContext, EventBus, ElsaClient, ElsaApiClientProvider} from "../../services"; +import {StudioService, AuthContext, EventBus, ElsaClient, ElsaClientProvider} from "../../services"; import descriptorsStore from '../../data/descriptors-store'; import {SignedInArgs} from "./models"; import {AxiosInstance} from "axios"; @@ -32,7 +32,7 @@ export class LoginPlugin implements Plugin { } private loadDescriptors = async (): Promise => { - const elsaClientProvider = Container.get(ElsaApiClientProvider); + const elsaClientProvider = Container.get(ElsaClientProvider); this.elsaClient = await elsaClientProvider.getElsaClient(); const activityDescriptors = await this.elsaClient.descriptors.activities.list(); diff --git a/src/designer/elsa-workflows-designer/src/modules/login/services.ts b/src/designer/elsa-workflows-designer/src/modules/login/services.ts index 0e63d209e..8c40a0727 100644 --- a/src/designer/elsa-workflows-designer/src/modules/login/services.ts +++ b/src/designer/elsa-workflows-designer/src/modules/login/services.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import {Container, Service} from "typedi"; -import {ElsaApiClientProvider, EventBus, ServerSettings} from "../../services"; +import {ElsaClientProvider, EventBus, ServerSettings} from "../../services"; import {LoginResponse} from "./models"; import axios, {AxiosError, AxiosRequestConfig} from "axios"; import {EventTypes} from "../../models"; @@ -8,7 +8,7 @@ import {EventTypes} from "../../models"; @Service() export class LoginApi { - constructor(private provider: ElsaApiClientProvider, private serverSettings: ServerSettings) { + constructor(private provider: ElsaClientProvider, private serverSettings: ServerSettings) { this.provider = provider; } diff --git a/src/designer/elsa-workflows-designer/src/modules/switch/switch-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/switch/switch-editor.tsx index 5e86241ce..6d8854cba 100644 --- a/src/designer/elsa-workflows-designer/src/modules/switch/switch-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/switch/switch-editor.tsx @@ -1,6 +1,6 @@ import {Component, h, Prop, State, Watch} from "@stencil/core"; import {camelCase} from 'lodash'; -import {ActivityInputContext} from "../../services/node-input-driver"; +import {ActivityInputContext} from "../../services/activity-input-driver"; import {mapSyntaxToLanguage} from "../../utils"; import {SyntaxNames} from "../../models"; import {SwitchCase} from "./models"; @@ -29,7 +29,7 @@ export class SwitchEditor { private updateCases(){ const inputContext = this.inputContext; - const activity = this.inputContext.node; + const activity = this.inputContext.activity; const inputDescriptor = inputContext.inputDescriptor; const propertyName = inputDescriptor.name; const camelCasePropertyName = camelCase(propertyName); @@ -68,7 +68,7 @@ export class SwitchEditor { private updateActivity = () => { const inputContext = this.inputContext; - const activity = this.inputContext.node; + const activity = this.inputContext.activity; const inputDescriptor = inputContext.inputDescriptor; const propertyName = inputDescriptor.name; const camelCasePropertyName = camelCase(propertyName); diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx index 576a0838e..0fcc46f89 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx @@ -4,12 +4,12 @@ import {v4 as uuid} from 'uuid'; import {Activity, ActivityDescriptor, ActivityInput, ActivityKind, ActivityOutput, Expression, InputDescriptor, OutputDescriptor, PropertyDescriptor, RenderActivityInputContext, RenderActivityPropsContext, TabChangedArgs, TabDefinition, Variable} from '../../../models'; import {InputDriverRegistry} from "../../../services"; import {Container} from "typedi"; -import {ActivityInputContext} from "../../../services/node-input-driver"; +import {ActivityInputContext} from "../../../services/activity-input-driver"; import {CheckboxFormEntry, FormEntry} from "../../../components/shared/forms/form-entry"; import {isNullOrWhitespace} from "../../../utils"; import descriptorsStore from "../../../data/descriptors-store"; import {ActivityUpdatedArgs, DeleteActivityRequestedArgs} from "../models/ui"; -import {WorkflowDefinition} from "../models/entities"; +import InputControlSwitchContextState from "../../../components/designer/input-control-switch/state"; @Component({ tag: 'elsa-activity-properties-editor', @@ -23,7 +23,8 @@ export class ActivityPropertiesEditor { this.inputDriverRegistry = Container.get(InputDriverRegistry); } - @Prop() workflowDefinition: WorkflowDefinition; + @Prop() containerType: string; + @Prop() containerId: string; @Prop() activity?: Activity; @Prop() variables: Array = []; @@ -59,15 +60,23 @@ export class ActivityPropertiesEditor { const renderInputPropertyContexts: Array = activityDescriptor.inputs.map(inputDescriptor => { const renderInputContext: ActivityInputContext = { - node: activity, - nodeDescriptor: activityDescriptor, + activity: activity, + activityDescriptor: activityDescriptor, inputDescriptor, notifyInputChanged: () => onInputChanged(inputDescriptor), inputChanged: (v, s) => this.onInputPropertyEditorChanged(inputDescriptor, v, s) }; const driver = driverRegistry.get(renderInputContext); - const control = driver?.renderInput(renderInputContext); + const containerType = this.containerType; + const containerId = this.containerId; + const activityType = activityDescriptor.typeName; + const propertyName = inputDescriptor.name; + + const control = + + {driver?.renderInput(renderInputContext)} + ; return { inputContext: renderInputContext, @@ -86,6 +95,7 @@ export class ActivityPropertiesEditor { render() { const {activity, activityDescriptor} = this.renderContext; const isTask = activityDescriptor.kind == ActivityKind.Task; + const activityType = activityDescriptor.typeName; const commonTab: TabDefinition = { displayText: 'General', diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx index 2a0402e17..813859605 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx @@ -14,7 +14,7 @@ import {MonacoEditorSettings} from "../../../services/monaco-editor-settings"; import {ActivityPropertyChangedEventArgs, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs, ActivityUpdatedArgs, WorkflowEditorEventTypes} from "../models/ui"; import {WorkflowDefinition} from "../models/entities"; import {WorkflowDefinitionsApi} from "../services/api" -import WorkflowDefinitionTunnel, {WorkflowDefinitionState} from "../../../state/workflow-definition-state"; +import WorkflowDefinitionTunnel, {WorkflowDefinitionState} from "../state"; import {LayoutDirection} from "../../flowchart/models"; import { cloneDeep } from '@antv/x6/lib/util/object/object'; import { removeGuidsFromPortNames } from '../../../utils/graph'; @@ -36,15 +36,13 @@ export class WorkflowDefinitionEditor { private readonly emitActivityChangedDebounced: (e: ActivityPropertyChangedEventArgs) => void; private readonly saveChangesDebounced: () => void; private readonly workflowDefinitionApi: WorkflowDefinitionsApi; - private currentFlowchartConnections; constructor() { this.eventBus = Container.get(EventBus); this.pluginRegistry = Container.get(PluginRegistry); this.activityNameFormatter = Container.get(ActivityNameFormatter); this.portProviderRegistry = Container.get(PortProviderRegistry); - //this.emitActivityChangedDebounced = debounce(this.emitActivityChanged, 100); - this.emitActivityChangedDebounced = e => this.emitActivityChanged(e.activity, e.propertyName); + this.emitActivityChangedDebounced = debounce(this.emitActivityChanged, 10); this.saveChangesDebounced = debounce(this.saveChanges, 1000); this.workflowDefinitionApi = Container.get(WorkflowDefinitionsApi); } @@ -174,9 +172,10 @@ export class WorkflowDefinitionEditor { private renderSelectedObject = () => { if (!!this.selectedActivity) return this.onActivityUpdated(e)}/>; } @@ -203,7 +202,7 @@ export class WorkflowDefinitionEditor { } }; - // To prevent redundant post requests to server, save changes only if there is a difference + // To prevent redundant post requests to server, save changes only if there is a difference // between existing workflow definition on server side and updated workflow definition on client side. private hasWorkflowDefinitionAnyUpdatedData = async (updatedWorkflowDefinition : WorkflowDefinition): Promise => { const existingWorkflowDefinition = await this.workflowDefinitionApi.get({definitionId: updatedWorkflowDefinition.definitionId, versionOptions: {version: updatedWorkflowDefinition.version}}); diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts index a11a83a50..854960535 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/services/api.ts @@ -2,16 +2,16 @@ import {getVersionOptionsString, serializeQueryString} from '../../../utils'; import {WorkflowDefinition, WorkflowDefinitionSummary, WorkflowOptions} from "../models/entities"; import {Activity, PagedList, Variable, VersionedEntity, VersionOptions} from "../../../models"; import {Service} from "typedi"; -import {ElsaApiClientProvider} from "../../../services"; import {AxiosResponse} from "axios"; import { removeGuidsFromPortNames, addGuidsToPortNames } from '../../../utils/graph'; import { cloneDeep } from '@antv/x6/lib/util/object/object'; +import {ElsaClientProvider} from "../../../services"; @Service() export class WorkflowDefinitionsApi { - private provider: ElsaApiClientProvider; + private provider: ElsaClientProvider; - constructor(provider: ElsaApiClientProvider) { + constructor(provider: ElsaClientProvider) { this.provider = provider; } diff --git a/src/designer/elsa-workflows-designer/src/state/workflow-definition-state.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/state.tsx similarity index 81% rename from src/designer/elsa-workflows-designer/src/state/workflow-definition-state.tsx rename to src/designer/elsa-workflows-designer/src/modules/workflow-definitions/state.tsx index c256d06c5..50789369b 100644 --- a/src/designer/elsa-workflows-designer/src/state/workflow-definition-state.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/state.tsx @@ -1,6 +1,6 @@ import {h} from "@stencil/core"; import {createProviderConsumer} from "@stencil/state-tunnel"; -import {WorkflowDefinition} from "../modules/workflow-definitions/models/entities"; +import {WorkflowDefinition} from "./models/entities"; export interface WorkflowDefinitionState { workflowDefinition: WorkflowDefinition; diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-instances/services/api.ts b/src/designer/elsa-workflows-designer/src/modules/workflow-instances/services/api.ts index d43b2298a..9eedc886e 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-instances/services/api.ts +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-instances/services/api.ts @@ -1,14 +1,14 @@ import {OrderBy, OrderDirection, PagedList, VersionOptions, WorkflowExecutionLogRecord, WorkflowInstance, WorkflowInstanceSummary, WorkflowStatus, WorkflowSubStatus} from "../../../models"; import {getVersionOptionsString, serializeQueryString} from "../../../utils"; -import {ElsaApiClientProvider} from "../../../services"; import {Service} from "typedi"; +import {ElsaClientProvider} from "../../../services"; @Service() export class WorkflowInstancesApi { - private provider: ElsaApiClientProvider; + private provider: ElsaClientProvider; - constructor(provider: ElsaApiClientProvider) { + constructor(provider: ElsaClientProvider) { this.provider = provider; } diff --git a/src/designer/elsa-workflows-designer/src/services/activity-descriptor-manager.ts b/src/designer/elsa-workflows-designer/src/services/activity-descriptor-manager.ts index a5c7562e2..d88b58897 100644 --- a/src/designer/elsa-workflows-designer/src/services/activity-descriptor-manager.ts +++ b/src/designer/elsa-workflows-designer/src/services/activity-descriptor-manager.ts @@ -1,14 +1,14 @@ import 'reflect-metadata'; import {Container, Service} from "typedi"; -import {ElsaApiClientProvider} from "./api-client/api-client"; import descriptorsStore from "../data/descriptors-store"; +import {ElsaClientProvider} from "./api-client/elsa-client"; @Service() export class ActivityDescriptorManager { - private readonly elsaClientProvider: ElsaApiClientProvider; + private readonly elsaClientProvider: ElsaClientProvider; constructor() { - this.elsaClientProvider = Container.get(ElsaApiClientProvider); + this.elsaClientProvider = Container.get(ElsaClientProvider); } async refresh(): Promise { diff --git a/src/designer/elsa-workflows-designer/src/services/node-input-driver.ts b/src/designer/elsa-workflows-designer/src/services/activity-input-driver.ts similarity index 78% rename from src/designer/elsa-workflows-designer/src/services/node-input-driver.ts rename to src/designer/elsa-workflows-designer/src/services/activity-input-driver.ts index a41a3880a..8c45424e6 100644 --- a/src/designer/elsa-workflows-designer/src/services/node-input-driver.ts +++ b/src/designer/elsa-workflows-designer/src/services/activity-input-driver.ts @@ -1,14 +1,14 @@ import {Activity, ActivityDescriptor, InputDescriptor} from "../models"; export interface ActivityInputContext { - node: Activity; - nodeDescriptor: ActivityDescriptor; + activity: Activity; + activityDescriptor: ActivityDescriptor; inputDescriptor: InputDescriptor; notifyInputChanged: () => void; inputChanged: (value: any, syntax: string) => void; } -export interface NodeInputDriver { +export interface ActivityInputDriver { supportsInput(context: ActivityInputContext): boolean; get priority(): number; diff --git a/src/designer/elsa-workflows-designer/src/services/api-client/designer-api.ts b/src/designer/elsa-workflows-designer/src/services/api-client/designer-api.ts index b1d816616..ff335b027 100644 --- a/src/designer/elsa-workflows-designer/src/services/api-client/designer-api.ts +++ b/src/designer/elsa-workflows-designer/src/services/api-client/designer-api.ts @@ -1,11 +1,7 @@ import {RuntimeSelectListApi} from "./runtime-select-list-api"; import {AxiosInstance} from "axios"; -export interface DesignerApi { - runtimeSelectListApi: RuntimeSelectListApi; -} - -export class DesignerApiImpl implements DesignerApi { +export class DesignerApi { runtimeSelectListApi: RuntimeSelectListApi; private httpClient: AxiosInstance; diff --git a/src/designer/elsa-workflows-designer/src/services/api-client/api-client.ts b/src/designer/elsa-workflows-designer/src/services/api-client/elsa-client.ts similarity index 83% rename from src/designer/elsa-workflows-designer/src/services/api-client/api-client.ts rename to src/designer/elsa-workflows-designer/src/services/api-client/elsa-client.ts index f87ddb97a..356c88d12 100644 --- a/src/designer/elsa-workflows-designer/src/services/api-client/api-client.ts +++ b/src/designer/elsa-workflows-designer/src/services/api-client/elsa-client.ts @@ -1,27 +1,28 @@ -import axios, {AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios"; +import axios, {AxiosInstance, AxiosRequestConfig} from "axios"; import {Service as MiddlewareService} from 'axios-middleware'; import {Container, Service} from 'typedi'; import {EventBus} from '../event-bus'; import 'reflect-metadata'; import {ServerSettings} from '../server-settings'; import {DescriptorsApi} from "./descriptors-api"; -import {DesignerApi, DesignerApiImpl} from "./designer-api"; +import {DesignerApi} from "./designer-api"; import {EventTypes} from "../../models"; +import {ScriptingApi} from "./scripting-api"; export class ElsaClient { - httpClient: AxiosInstance; descriptors: DescriptorsApi; designer: DesignerApi; + scripting: ScriptingApi; constructor(httpClient: AxiosInstance) { - this.httpClient = httpClient; this.descriptors = new DescriptorsApi(httpClient); - this.designer = new DesignerApiImpl(httpClient); + this.designer = new DesignerApi(httpClient); + this.scripting = new ScriptingApi(httpClient); } } @Service() -export class ElsaApiClientProvider { +export class ElsaClientProvider { private httpClient: AxiosInstance; private elsaClient: ElsaClient; diff --git a/src/designer/elsa-workflows-designer/src/services/api-client/javascript-api.ts b/src/designer/elsa-workflows-designer/src/services/api-client/javascript-api.ts new file mode 100644 index 000000000..c420168ce --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/services/api-client/javascript-api.ts @@ -0,0 +1,19 @@ +import {AxiosInstance} from "axios"; + +export class JavaScriptApi { + + constructor(private httpClient: AxiosInstance) { + } + + async getTypeDefinitions(request: GetTypeDefinitionsRequest): Promise { + const response = await this.httpClient.post(`scripting/javascript/type-definitions/${request.containerId}`, request); + return response.data; + } +} + +export interface GetTypeDefinitionsRequest { + containerType: string; + containerId: string; + activityTypeName?: string; + propertyName?: string; +} diff --git a/src/designer/elsa-workflows-designer/src/services/api-client/scripting-api.ts b/src/designer/elsa-workflows-designer/src/services/api-client/scripting-api.ts new file mode 100644 index 000000000..558207d07 --- /dev/null +++ b/src/designer/elsa-workflows-designer/src/services/api-client/scripting-api.ts @@ -0,0 +1,11 @@ +import {AxiosInstance} from "axios"; +import {JavaScriptApi} from "./javascript-api"; + +export class ScriptingApi { + javaScriptApi: JavaScriptApi; + + constructor(httpClient: AxiosInstance) { + this.javaScriptApi = new JavaScriptApi(httpClient); + } + +} diff --git a/src/designer/elsa-workflows-designer/src/services/index.ts b/src/designer/elsa-workflows-designer/src/services/index.ts index 80fe2bc38..b66d7941e 100644 --- a/src/designer/elsa-workflows-designer/src/services/index.ts +++ b/src/designer/elsa-workflows-designer/src/services/index.ts @@ -4,7 +4,7 @@ export * from './activity-driver-registry'; export * from './activity-icon-registry'; export * from './auth'; export * from './custom-event-bus'; -export * from './api-client/api-client'; +export * from './api-client/elsa-client'; export * from './event-bus'; export * from './input-control-registry'; export * from './input-driver-registry'; diff --git a/src/designer/elsa-workflows-designer/src/services/input-driver-registry.ts b/src/designer/elsa-workflows-designer/src/services/input-driver-registry.ts index 0f4393a65..8ab3efa5f 100644 --- a/src/designer/elsa-workflows-designer/src/services/input-driver-registry.ts +++ b/src/designer/elsa-workflows-designer/src/services/input-driver-registry.ts @@ -1,18 +1,18 @@ import _ from 'lodash'; import {Service} from 'typedi'; -import {NodeInputDriver, ActivityInputContext} from './node-input-driver'; +import {ActivityInputDriver, ActivityInputContext} from './activity-input-driver'; import {DefaultInputDriver} from "../drivers/input/default-input-driver"; @Service() export class InputDriverRegistry { - private drivers: Array = [new DefaultInputDriver()]; + private drivers: Array = [new DefaultInputDriver()]; - public add(driver: NodeInputDriver) { - const drivers: Array = [...this.drivers, driver] + public add(driver: ActivityInputDriver) { + const drivers: Array = [...this.drivers, driver] this.drivers = _.orderBy(drivers, x => x.priority, 'desc'); } - public get(context: ActivityInputContext): NodeInputDriver { + public get(context: ActivityInputContext): ActivityInputDriver { return this.drivers.find(x => x.supportsInput(context)); } } diff --git a/src/designer/elsa-workflows-designer/src/utils/select-list-items.ts b/src/designer/elsa-workflows-designer/src/utils/select-list-items.ts index 07fb78754..fec487dc4 100644 --- a/src/designer/elsa-workflows-designer/src/utils/select-list-items.ts +++ b/src/designer/elsa-workflows-designer/src/utils/select-list-items.ts @@ -3,11 +3,11 @@ import { RuntimeSelectListProviderSettings, SelectList, } from "../models"; -import {ElsaApiClientProvider} from "../services"; import {Container} from "typedi"; +import {ElsaClientProvider} from "../services"; async function fetchRuntimeItems(options: RuntimeSelectListProviderSettings): Promise { - const elsaClientProvider = Container.get(ElsaApiClientProvider); + const elsaClientProvider = Container.get(ElsaClientProvider); const elsaClient = await elsaClientProvider.getElsaClient(); return await elsaClient.designer.runtimeSelectListApi.get(options.runtimeSelectListProviderType, options.context || {}); } diff --git a/src/designer/elsa-workflows-designer/src/utils/utils.ts b/src/designer/elsa-workflows-designer/src/utils/utils.ts index a6feb7c6b..f574ccb81 100644 --- a/src/designer/elsa-workflows-designer/src/utils/utils.ts +++ b/src/designer/elsa-workflows-designer/src/utils/utils.ts @@ -1,7 +1,7 @@ import moment from 'moment'; import _, {camelCase} from 'lodash'; import {ActivityInput, VersionOptions} from '../models'; -import {ActivityInputContext} from '../services/node-input-driver'; +import {ActivityInputContext} from '../services/activity-input-driver'; export interface Hash { [key: string]: TValue; @@ -71,12 +71,12 @@ export const getInputPropertyName = (inputContext: ActivityInputContext) => { export const getInputPropertyValue = (inputContext: ActivityInputContext): ActivityInput => { const propName = getInputPropertyName(inputContext); - return inputContext.node[propName] as ActivityInput; + return inputContext.activity[propName] as ActivityInput; }; export const getPropertyValue = (inputContext: ActivityInputContext): any => { const propName = getInputPropertyName(inputContext); - return inputContext.node[propName] as any + return inputContext.activity[propName] as any }; export const stripActivityNameSpace = (name: string): string => { diff --git a/src/designer/elsa-workflows-designer/tsconfig.json b/src/designer/elsa-workflows-designer/tsconfig.json index 00ea3de98..1c44053f2 100644 --- a/src/designer/elsa-workflows-designer/tsconfig.json +++ b/src/designer/elsa-workflows-designer/tsconfig.json @@ -7,8 +7,7 @@ "emitDecoratorMetadata": true, "lib": [ "dom", - "es2017", - "es2018.promise" + "ES2020" ], "moduleResolution": "node", "module": "esnext", diff --git a/src/modules/Elsa.Expressions/Extensions/WellKnowTypeRegistryExtensions.cs b/src/modules/Elsa.Expressions/Extensions/WellKnowTypeRegistryExtensions.cs new file mode 100644 index 000000000..3c86cfcb5 --- /dev/null +++ b/src/modules/Elsa.Expressions/Extensions/WellKnowTypeRegistryExtensions.cs @@ -0,0 +1,43 @@ +using Elsa.Expressions.Services; +using Elsa.Extensions; + +namespace Elsa.Expressions.Extensions; + +/// +/// Extends . +/// +public static class WellKnowTypeRegistryExtensions +{ + /// + /// Register type with the specified alias. + /// + public static void RegisterType(this IWellKnownTypeRegistry registry, string alias) => registry.RegisterType(typeof(T), alias); + + /// + /// Attempt to return a type with the specified alias. + /// + public static bool TryGetTypeOrDefault(this IWellKnownTypeRegistry registry, string alias, out Type type) + { + if (registry.TryGetType(alias, out type)) + return true; + + var t = Type.GetType(alias); + + if (t == null) + return false; + + type = t; + return true; + } + + /// + /// Returns the alias for the specified type. If no alias was found, the assembly qualified type name is returned instead. + /// + public static string GetAliasOrDefault(this IWellKnownTypeRegistry registry, Type type) => + registry.TryGetAlias(type, out var alias) ? alias : type.GetSimpleAssemblyQualifiedName(); + + /// + /// Returns the type associated with the specified alias. If no type was found, the alias is interpreted as a type name/ + /// + public static Type GetTypeOrDefault(this IWellKnownTypeRegistry registry, string alias) => registry.TryGetType(alias, out var type) ? type : Type.GetType(alias)!; +} \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index edbd73b11..949b63f12 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Text.Json; using System.Text.Json.Serialization; using Elsa.Expressions.Exceptions; +using Elsa.Expressions.Extensions; using Elsa.Expressions.Models; using Elsa.Expressions.Services; using Elsa.Extensions; diff --git a/src/modules/Elsa.Expressions/Implementations/WellKnownTypeRegistry.cs b/src/modules/Elsa.Expressions/Implementations/WellKnownTypeRegistry.cs index da5d42638..81bf2a2cb 100644 --- a/src/modules/Elsa.Expressions/Implementations/WellKnownTypeRegistry.cs +++ b/src/modules/Elsa.Expressions/Implementations/WellKnownTypeRegistry.cs @@ -1,3 +1,4 @@ +using Elsa.Expressions.Extensions; using Elsa.Expressions.Services; namespace Elsa.Expressions.Implementations; @@ -41,4 +42,7 @@ public class WellKnownTypeRegistry : IWellKnownTypeRegistry /// public bool TryGetType(string alias, out Type type) => _aliasTypeDictionary.TryGetValue(alias, out type!); + + /// + public IEnumerable ListTypes() => _typeAliasDictionary.Keys; } \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Services/IWellKnownTypeRegistry.cs b/src/modules/Elsa.Expressions/Services/IWellKnownTypeRegistry.cs index 85620fe0f..a88e71991 100644 --- a/src/modules/Elsa.Expressions/Services/IWellKnownTypeRegistry.cs +++ b/src/modules/Elsa.Expressions/Services/IWellKnownTypeRegistry.cs @@ -1,34 +1,27 @@ -using Elsa.Extensions; - namespace Elsa.Expressions.Services; +/// +/// A central repository of well known types. +/// public interface IWellKnownTypeRegistry { + /// + /// Register a type with an alias. + /// void RegisterType(Type type, string alias); - bool TryGetAlias(Type type, out string alias); - bool TryGetType(string alias, out Type type); -} - -public static class WellKnowTypeRegistryExtensions -{ - public static void RegisterType(this IWellKnownTypeRegistry registry, string alias) => registry.RegisterType(typeof(T), alias); - - public static bool TryGetTypeOrDefault(this IWellKnownTypeRegistry registry, string alias, out Type type) - { - if (registry.TryGetType(alias, out type)) - return true; - - var t = Type.GetType(alias); - - if (t == null) - return false; - - type = t; - return true; - } - - public static string GetAliasOrDefault(this IWellKnownTypeRegistry registry, Type type) => - registry.TryGetAlias(type, out var alias) ? alias : type.GetSimpleAssemblyQualifiedName(); - public static Type GetTypeOrDefault(this IWellKnownTypeRegistry registry, string alias) => registry.TryGetType(alias, out var type) ? type : Type.GetType(alias)!; + /// + /// Attempts to get an alias for the specified type. + /// + bool TryGetAlias(Type type, out string alias); + + /// + /// Attempts to get the type associated with the specified alias. + /// + bool TryGetType(string alias, out Type type); + + /// + /// Returns all registered types. + /// + IEnumerable ListTypes(); } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Abstractions/FunctionDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Abstractions/FunctionDefinitionProvider.cs new file mode 100644 index 000000000..560be8f49 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Abstractions/FunctionDefinitionProvider.cs @@ -0,0 +1,40 @@ +using Elsa.JavaScript.Builders; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Abstractions; + +/// +/// A base class for function definition providers. +/// +public abstract class FunctionDefinitionProvider : IFunctionDefinitionProvider +{ + /// + /// Returns a list of type definitions to the system. + /// + protected virtual ValueTask> GetFunctionDefinitionsAsync(TypeDefinitionContext context) + { + var functions = GetFunctionDefinitions(context); + return new(functions); + } + + /// + /// Returns a list of type definitions to the system. + /// + protected virtual IEnumerable GetFunctionDefinitions(TypeDefinitionContext context) + { + yield break; + } + + async ValueTask> IFunctionDefinitionProvider.GetFunctionDefinitionsAsync(TypeDefinitionContext context) => await GetFunctionDefinitionsAsync(context); + + /// + /// Provides a fluid API to build a function definition. + /// + protected FunctionDefinition CreateFunctionDefinition(Action setup) + { + var builder = new FunctionDefinitionBuilder(); + setup(builder); + return builder.BuildFunctionDefinition(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Abstractions/TypeDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Abstractions/TypeDefinitionProvider.cs new file mode 100644 index 000000000..ea95d1a64 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Abstractions/TypeDefinitionProvider.cs @@ -0,0 +1,40 @@ +using Elsa.JavaScript.Builders; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Abstractions; + +/// +/// A base class for type definition providers. +/// +public abstract class TypeDefinitionProvider : ITypeDefinitionProvider +{ + /// + /// Returns a list of type definitions to the system. + /// + protected virtual ValueTask> GetTypeDefinitionsAsync(TypeDefinitionContext context) + { + var functions = GetTypeDefinitions(context); + return new(functions); + } + + /// + /// Returns a list of type definitions to the system. + /// + protected virtual IEnumerable GetTypeDefinitions(TypeDefinitionContext context) + { + yield break; + } + + async ValueTask> ITypeDefinitionProvider.GetTypeDefinitionsAsync(TypeDefinitionContext context) => await GetTypeDefinitionsAsync(context); + + /// + /// Provides a fluid API to build a type definition. + /// + protected TypeDefinition CreateTypeDefinition(Action setup) + { + var builder = new TypeDefinitionBuilder(); + setup(builder); + return builder.BuildTypeDefinition(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Builders/FunctionDefinitionBuilder.cs b/src/modules/Elsa.JavaScript/Builders/FunctionDefinitionBuilder.cs new file mode 100644 index 000000000..c43ad8323 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Builders/FunctionDefinitionBuilder.cs @@ -0,0 +1,43 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Builders; + +/// +/// A builder API for building s. +/// +public class FunctionDefinitionBuilder +{ + private readonly FunctionDefinition _functionDefinition = new(); + + /// + /// Set the name of the function. + /// + public FunctionDefinitionBuilder Name(string name) + { + _functionDefinition.Name = name; + return this; + } + + /// + /// Set the return type of the function. + /// + public FunctionDefinitionBuilder ReturnType(string? type) + { + _functionDefinition.ReturnType = type; + return this; + } + + /// + /// Add a parameter to the function. + /// + public FunctionDefinitionBuilder Parameter(string name, string? type, bool isOptional = false) + { + _functionDefinition.Parameters.Add(new (name, type, isOptional)); + return this; + } + + /// + /// Build a using the collected information. + /// + public FunctionDefinition BuildFunctionDefinition() => new(_functionDefinition.Name, _functionDefinition.ReturnType, _functionDefinition.Parameters); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Builders/TypeDefinitionBuilder.cs b/src/modules/Elsa.JavaScript/Builders/TypeDefinitionBuilder.cs new file mode 100644 index 000000000..2eb6b48f9 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Builders/TypeDefinitionBuilder.cs @@ -0,0 +1,17 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Builders; + +/// +/// A builder API for building s. +/// +public class TypeDefinitionBuilder +{ + private readonly TypeDefinition _typeDefinition = new(); + + + /// + /// Build a using the collected information. + /// + public TypeDefinition BuildTypeDefinition() => new(); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs index e89fd3e8e..82ce33ac2 100644 --- a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs +++ b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs @@ -4,7 +4,13 @@ using Jint.Runtime.Interop; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; +/// +/// Extends . +/// public static class EngineExtensions { + /// + /// Register type with the engine. + /// public static void RegisterType(this Engine engine) => engine.SetValue(typeof(T).Name, TypeReference.CreateTypeReference(engine, typeof(T))); } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Extensions/TypeAliasRegistryExtensions.cs b/src/modules/Elsa.JavaScript/Extensions/TypeAliasRegistryExtensions.cs new file mode 100644 index 000000000..fc131b6b5 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Extensions/TypeAliasRegistryExtensions.cs @@ -0,0 +1,21 @@ +using Elsa.Extensions; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Extensions; + +/// +/// Extends . +/// +public static class TypeAliasRegistryExtensions +{ + /// + /// Register type with the specified alias. + /// + public static void RegisterType(this ITypeAliasRegistry registry, string alias) => registry.RegisterType(typeof(T), alias); + + /// + /// Returns the alias for the specified type. If no alias was found, the assembly qualified type name is returned instead. + /// + public static string GetAliasOrDefault(this ITypeAliasRegistry registry, Type type) => + registry.TryGetAlias(type, out var alias) ? alias : type.GetSimpleAssemblyQualifiedName(); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs index 05de4f92f..f0aa0b4e8 100644 --- a/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs +++ b/src/modules/Elsa.JavaScript/Features/JavaScriptFeature.cs @@ -34,9 +34,18 @@ public class JavaScriptFeature : FeatureBase .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddExpressionHandler() ; + Services + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + Module.UseWorkflowManagement(management => management.AddActivitiesFrom()); } } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Implementations/TypeAliasRegistry.cs b/src/modules/Elsa.JavaScript/Implementations/TypeAliasRegistry.cs new file mode 100644 index 000000000..e04a2b1b5 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Implementations/TypeAliasRegistry.cs @@ -0,0 +1,39 @@ +using Elsa.JavaScript.Extensions; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Implementations; + +/// +public class TypeAliasRegistry : ITypeAliasRegistry +{ + private readonly IDictionary _typeAliasDictionary = new Dictionary(); + + /// + /// Constructor. + /// + public TypeAliasRegistry() + { + this.RegisterType("any"); + this.RegisterType("string"); + this.RegisterType("boolean"); + this.RegisterType("number"); + this.RegisterType("number"); + this.RegisterType("number"); + this.RegisterType("Decimal"); + this.RegisterType("Single"); + this.RegisterType("Double"); + this.RegisterType("Date"); + this.RegisterType("Date"); + this.RegisterType("Date"); + this.RegisterType("Date"); + } + + /// + public void RegisterType(Type type, string alias) + { + _typeAliasDictionary[type] = alias; + } + + /// + public bool TryGetAlias(Type type, out string alias) => _typeAliasDictionary.TryGetValue(type, out alias!); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionDocumentRenderer.cs b/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionDocumentRenderer.cs new file mode 100644 index 000000000..a731c815c --- /dev/null +++ b/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionDocumentRenderer.cs @@ -0,0 +1,45 @@ +using System.Text; +using Elsa.JavaScript.Extensions; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Implementations; + +/// +public class TypeDefinitionDocumentRenderer : ITypeDefinitionDocumentRenderer +{ + /// + public string Render(TypeDefinitionsDocument document) + { + var stringBuilder = new StringBuilder(); + + foreach (var functionDefinition in document.Functions) + Render(functionDefinition, stringBuilder); + + foreach (var typeDefinition in document.Types) + Render(typeDefinition, stringBuilder); + + return stringBuilder.ToString(); + } + + private void Render(FunctionDefinition functionDefinition, StringBuilder output) + { + string RenderParameter(ParameterDefinition parameter) => $"{parameter.Name}{(parameter.IsOptional ? "?" : "")}: {parameter.Type}"; + string RenderParameters(IEnumerable parameters) => string.Join(", ", parameters.Select(RenderParameter)); + + var returnType = functionDefinition.ReturnType != null ? $": {functionDefinition.ReturnType}" : ""; + output.AppendLine($"declare function {functionDefinition.Name}({RenderParameters(functionDefinition.Parameters)}){returnType}"); + } + + private void Render(TypeDefinition typeDefinition, StringBuilder output) + { + output.AppendLine($"declare {typeDefinition.DeclarationKeyword} {typeDefinition.Name} {{"); + + foreach (var property in typeDefinition.Properties) + Render(property, output); + + output.AppendLine("}"); + } + + private void Render(PropertyDefinition property, StringBuilder output) => output.AppendLine($"{property.Name}{(property.IsOptional ? "?" : "")}: {property.Type}"); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionService.cs b/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionService.cs new file mode 100644 index 000000000..d218fc6f6 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Implementations/TypeDefinitionService.cs @@ -0,0 +1,62 @@ +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Implementations; + +/// +public class TypeDefinitionService : ITypeDefinitionService +{ + private readonly IEnumerable _typeDefinitionProviders; + private readonly IEnumerable _functionDefinitionProviders; + private readonly ITypeDefinitionDocumentRenderer _typeDefinitionDocumentRenderer; + + /// + /// Constructor. + /// + public TypeDefinitionService( + IEnumerable typeDefinitionProviders, + IEnumerable functionDefinitionProviders, + ITypeDefinitionDocumentRenderer typeDefinitionDocumentRenderer) + { + _typeDefinitionProviders = typeDefinitionProviders; + _functionDefinitionProviders = functionDefinitionProviders; + _typeDefinitionDocumentRenderer = typeDefinitionDocumentRenderer; + } + + /// + public async Task GenerateTypeDefinitionsAsync(TypeDefinitionContext context) + { + var typeDefinitions = await GetTypeDefinitionsAsync(context).ToListAsync(context.CancellationToken); + var functionDefinitions = await GetFunctionDefinitionsAsync(context).ToListAsync(context.CancellationToken); + + var document = new TypeDefinitionsDocument + { + Functions = functionDefinitions, + Types = typeDefinitions + }; + + return _typeDefinitionDocumentRenderer.Render(document); + } + + private async IAsyncEnumerable GetFunctionDefinitionsAsync(TypeDefinitionContext context) + { + foreach (var provider in _functionDefinitionProviders) + { + var definitions = await provider.GetFunctionDefinitionsAsync(context); + + foreach (var definition in definitions) + yield return definition; + } + } + + private async IAsyncEnumerable GetTypeDefinitionsAsync(TypeDefinitionContext context) + { + foreach (var provider in _typeDefinitionProviders) + { + var definitions = await provider.GetTypeDefinitionsAsync(context); + + foreach (var definition in definitions) + yield return definition; + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Implementations/TypeDescriber.cs b/src/modules/Elsa.JavaScript/Implementations/TypeDescriber.cs new file mode 100644 index 000000000..88d5f98aa --- /dev/null +++ b/src/modules/Elsa.JavaScript/Implementations/TypeDescriber.cs @@ -0,0 +1,90 @@ +using System.Reflection; +using Elsa.Extensions; +using Elsa.JavaScript.Extensions; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Implementations; + +/// +public class TypeDescriber : ITypeDescriber +{ + private readonly ITypeAliasRegistry _typeAliasRegistry; + + /// + /// Constructor. + /// + public TypeDescriber(ITypeAliasRegistry typeAliasRegistry) + { + _typeAliasRegistry = typeAliasRegistry; + } + + /// + public TypeDefinition DescribeType(Type type) + { + var typeDefinition = new TypeDefinition + { + DeclarationKeyword = GetDeclarationKeyword(type), + Name = type.Name, + Properties = GetPropertyDefinitions(type).ToList(), + Methods = GetMethodDefinitions(type).ToList() + }; + + return typeDefinition; + } + + private IEnumerable GetMethodDefinitions(Type type) + { + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Where(x => !x.IsSpecialName).ToList(); + + foreach (var method in methods) + { + yield return new FunctionDefinition + { + Name = method.Name, + Parameters = GetMethodParameters(method).ToList(), + ReturnType = _typeAliasRegistry.TryGetAlias(method.ReturnType, out var alias) ? alias : "any" + }; + } + } + + private IEnumerable GetMethodParameters(MethodInfo method) + { + var parameters = method.GetParameters(); + + foreach (var parameter in parameters) + { + yield return new ParameterDefinition + { + Name = parameter.Name!, + Type = _typeAliasRegistry.TryGetAlias(parameter.ParameterType, out var alias) ? alias : "any", + IsOptional = parameter.IsOptional + }; + } + } + + private IEnumerable GetPropertyDefinitions(Type type) + { + var properties = type.GetProperties(); + + foreach (var property in properties) + { + yield return new PropertyDefinition + { + Name = property.Name, + Type = _typeAliasRegistry.TryGetAlias(property.PropertyType, out var alias) ? alias : "any", + IsOptional = property.PropertyType.IsNullableType() + }; + } + } + + private static string GetDeclarationKeyword(Type type) => + type switch + { + { IsInterface: true } => "interface", + { IsClass: true } => "class", + { IsValueType: true } => "class", + { IsEnum: true } => "enum", + _ => "interface" + }; +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/FunctionDefinition.cs b/src/modules/Elsa.JavaScript/Models/FunctionDefinition.cs new file mode 100644 index 000000000..26411cb07 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/FunctionDefinition.cs @@ -0,0 +1,48 @@ +namespace Elsa.JavaScript.Models; + +/// +/// A method definition. +/// +public class FunctionDefinition +{ + /// + /// Constructor. + /// + public FunctionDefinition() + { + } + + /// + /// Constructor. + /// + public FunctionDefinition(string name, IEnumerable parameters) + { + Name = name; + Parameters = parameters.ToList(); + } + + /// + /// Constructor. + /// + public FunctionDefinition(string name, string? returnType, IEnumerable parameters) + { + Name = name; + ReturnType = returnType; + Parameters = parameters.ToList(); + } + + /// + /// The name of the method. + /// + public string Name { get; set; } = default!; + + /// + /// The return type name of the method. + /// + public string? ReturnType { get; set; } + + /// + /// The parameters of the method. + /// + public ICollection Parameters { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/ParameterDefinition.cs b/src/modules/Elsa.JavaScript/Models/ParameterDefinition.cs new file mode 100644 index 000000000..9eda673e0 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/ParameterDefinition.cs @@ -0,0 +1,39 @@ +namespace Elsa.JavaScript.Models; + +/// +/// A method parameter definition. +/// +public class ParameterDefinition +{ + /// + /// Constructor. + /// + public ParameterDefinition() + { + } + + /// + /// Constructor. + /// + public ParameterDefinition(string name, string? type, bool isOptional = false) + { + Name = name; + Type = type; + IsOptional = isOptional; + } + + /// + /// The name of the parameter. + /// + public string Name { get; set; } = default!; + + /// + /// The type name of the parameter. + /// + public string? Type { get; set; } + + /// + /// A value indicating whether the parameter is optional or not. + /// + public bool IsOptional { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/PropertyDefinition.cs b/src/modules/Elsa.JavaScript/Models/PropertyDefinition.cs new file mode 100644 index 000000000..f247df678 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/PropertyDefinition.cs @@ -0,0 +1,22 @@ +namespace Elsa.JavaScript.Models; + +/// +/// A property definition. +/// +public class PropertyDefinition +{ + /// + /// The name of the property. + /// + public string Name { get; set; } = default!; + + /// + /// The type name of the property. + /// + public string Type { get; set; } = default!; + + /// + /// A value indicating whether the property is optional or not. + /// + public bool IsOptional { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/TypeDefinition.cs b/src/modules/Elsa.JavaScript/Models/TypeDefinition.cs new file mode 100644 index 000000000..19832eaed --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/TypeDefinition.cs @@ -0,0 +1,28 @@ +namespace Elsa.JavaScript.Models; + + +/// +/// A base type that represents a type. +/// +public sealed class TypeDefinition +{ + /// + /// The keyword that declares this type. E.g. "class", "type" or "interface". + /// + public string DeclarationKeyword { get; set; } = default!; + + /// + /// The name of the type. + /// + public string Name { get; set; } = default!; + + /// + /// A list of fields. + /// + public ICollection Properties { get; set; } = new List(); + + /// + /// A list of methods. + /// + public ICollection Methods { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/TypeDefinitionContext.cs b/src/modules/Elsa.JavaScript/Models/TypeDefinitionContext.cs new file mode 100644 index 000000000..5918c04b1 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/TypeDefinitionContext.cs @@ -0,0 +1,8 @@ +using Elsa.Workflows.Core.Models; + +namespace Elsa.JavaScript.Models; + +/// +/// Provides context to intellisense providers. +/// +public record TypeDefinitionContext(ICollection Variables, string? ActivityTypeName, string? PropertyName, CancellationToken CancellationToken); \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Models/TypeDefinitionsDocument.cs b/src/modules/Elsa.JavaScript/Models/TypeDefinitionsDocument.cs new file mode 100644 index 000000000..547327bb1 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Models/TypeDefinitionsDocument.cs @@ -0,0 +1,10 @@ +namespace Elsa.JavaScript.Models; + +/// +/// Represents a type definition document model. +/// +public sealed class TypeDefinitionsDocument +{ + public ICollection Functions { get; set; } = new List(); + public ICollection Types { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Providers/CommonFunctionDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Providers/CommonFunctionDefinitionProvider.cs new file mode 100644 index 000000000..3f96e6869 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Providers/CommonFunctionDefinitionProvider.cs @@ -0,0 +1,58 @@ +using Elsa.Extensions; +using Elsa.JavaScript.Abstractions; +using Elsa.JavaScript.Extensions; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; +using Humanizer; + +namespace Elsa.JavaScript.Providers; + +/// +/// Produces s for common functions. +/// +internal class CommonFunctionDefinitionProvider : FunctionDefinitionProvider +{ + private readonly ITypeAliasRegistry _typeAliasRegistry; + + public CommonFunctionDefinitionProvider(ITypeAliasRegistry typeAliasRegistry) + { + _typeAliasRegistry = typeAliasRegistry; + } + + protected override IEnumerable GetFunctionDefinitions(TypeDefinitionContext context) + { + yield return CreateFunctionDefinition(builder => builder + .Name("setVariable") + .Parameter("name", "string") + .Parameter("value", "any")); + + yield return CreateFunctionDefinition(builder => builder + .Name("getVariable") + .Parameter("name", "string") + .ReturnType("any")); + + yield return CreateFunctionDefinition(builder => builder + .Name("isNullOrWhiteSpace") + .Parameter("value", "string") + .ReturnType("boolean")); + + yield return CreateFunctionDefinition(builder => builder + .Name("isNullOrEmpty") + .Parameter("value", "string") + .ReturnType("boolean")); + + // Variable getter and setters. + foreach (var variable in context.Variables) + { + var pascalName = variable.Name.Pascalize(); + var variableType = variable.GetVariableType(); + var typeAlias = _typeAliasRegistry.TryGetAlias(variableType, out var alias) ? alias : "any"; + + // get{Variable}. + yield return CreateFunctionDefinition(builder => builder.Name($"get{pascalName}").ReturnType(typeAlias)); + + // set{Variable}. + yield return CreateFunctionDefinition(builder => builder.Name($"set{pascalName}").Parameter("value", typeAlias)); + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Providers/JavaScriptExpressionSyntaxProvider.cs b/src/modules/Elsa.JavaScript/Providers/JavaScriptExpressionSyntaxProvider.cs index 655b96361..056360535 100644 --- a/src/modules/Elsa.JavaScript/Providers/JavaScriptExpressionSyntaxProvider.cs +++ b/src/modules/Elsa.JavaScript/Providers/JavaScriptExpressionSyntaxProvider.cs @@ -5,9 +5,9 @@ using Elsa.Workflows.Core.Services; namespace Elsa.JavaScript.Providers; -public class JavaScriptExpressionSyntaxProvider : IExpressionSyntaxProvider +internal class JavaScriptExpressionSyntaxProvider : IExpressionSyntaxProvider { - public const string SyntaxName = "JavaScript"; + private const string SyntaxName = "JavaScript"; private readonly IIdentityGenerator _identityGenerator; public JavaScriptExpressionSyntaxProvider(IIdentityGenerator identityGenerator) diff --git a/src/modules/Elsa.JavaScript/Providers/VariableTypeDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Providers/VariableTypeDefinitionProvider.cs new file mode 100644 index 000000000..88af27577 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Providers/VariableTypeDefinitionProvider.cs @@ -0,0 +1,38 @@ +using System.Dynamic; +using Elsa.Extensions; +using Elsa.JavaScript.Abstractions; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; + +namespace Elsa.JavaScript.Providers; + +/// +/// Produces s for variable types. +/// +internal class VariableTypeDefinitionProvider : TypeDefinitionProvider +{ + private readonly ITypeDescriber _typeDescriber; + + public VariableTypeDefinitionProvider(ITypeDescriber typeDescriber) + { + _typeDescriber = typeDescriber; + } + + protected override IEnumerable GetTypeDefinitions(TypeDefinitionContext context) + { + var excludedTypes = new[] { typeof(ExpandoObject), typeof(IDictionary) }; + + var variableTypeQuery = + from variable in context.Variables + let variableType = variable.GetVariableType() + where (variableType.IsClass || variableType.IsInterface) && !variableType.IsPrimitive && !excludedTypes.Any(x => x.IsAssignableFrom(variableType)) + select variableType; + + var variableTypes = variableTypeQuery.Distinct(); + + foreach (var variableType in variableTypes) + { + yield return _typeDescriber.DescribeType(variableType); + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/IFunctionDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Services/IFunctionDefinitionProvider.cs new file mode 100644 index 000000000..296ec976d --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/IFunctionDefinitionProvider.cs @@ -0,0 +1,14 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Services; + +/// +/// Provides s to the type definition document being constructed. +/// +public interface IFunctionDefinitionProvider +{ + /// + /// Returns a list of type definitions to the system. + /// + ValueTask> GetFunctionDefinitionsAsync(TypeDefinitionContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/ITypeAliasRegistry.cs b/src/modules/Elsa.JavaScript/Services/ITypeAliasRegistry.cs new file mode 100644 index 000000000..65b46f144 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/ITypeAliasRegistry.cs @@ -0,0 +1,17 @@ +namespace Elsa.JavaScript.Services; + +/// +/// A central registry of type aliases. +/// +public interface ITypeAliasRegistry +{ + /// + /// Register a type with an alias. + /// + void RegisterType(Type type, string alias); + + /// + /// Attempts to get an alias for the specified type. + /// + bool TryGetAlias(Type type, out string alias); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/ITypeDefinitionDocumentRenderer.cs b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionDocumentRenderer.cs new file mode 100644 index 000000000..8cf8c6b11 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionDocumentRenderer.cs @@ -0,0 +1,12 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Services; + +/// +/// Renders a to a string. +/// +public interface ITypeDefinitionDocumentRenderer +{ + /// Renders a to a string. + string Render(TypeDefinitionsDocument document); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/ITypeDefinitionProvider.cs b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionProvider.cs new file mode 100644 index 000000000..7e56ab9da --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionProvider.cs @@ -0,0 +1,14 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Services; + +/// +/// Provides s to the type definition document being constructed. +/// +public interface ITypeDefinitionProvider +{ + /// + /// Returns a list of type definitions to the system. + /// + ValueTask> GetTypeDefinitionsAsync(TypeDefinitionContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/ITypeDefinitionService.cs b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionService.cs new file mode 100644 index 000000000..f192b6514 --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/ITypeDefinitionService.cs @@ -0,0 +1,14 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Services; + +/// +/// Generates type definitions for the specified context. +/// +public interface ITypeDefinitionService +{ + /// + /// Generates type definitions for the specified context. + /// + Task GenerateTypeDefinitionsAsync(TypeDefinitionContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Services/ITypeDescriber.cs b/src/modules/Elsa.JavaScript/Services/ITypeDescriber.cs new file mode 100644 index 000000000..d50106fcd --- /dev/null +++ b/src/modules/Elsa.JavaScript/Services/ITypeDescriber.cs @@ -0,0 +1,14 @@ +using Elsa.JavaScript.Models; + +namespace Elsa.JavaScript.Services; + +/// +/// Returns a from a given . +/// +public interface ITypeDescriber +{ + /// + /// Returns a from a given . + /// + TypeDefinition DescribeType(Type type); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj index 0ba4cd3f2..bfe55e6a1 100644 --- a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj +++ b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj @@ -13,8 +13,14 @@ + + + + + + diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs new file mode 100644 index 000000000..66affc5d5 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/Scripting/JavaScript/TypeDefinitions/Endpoint.cs @@ -0,0 +1,78 @@ +using System.Text; +using Elsa.ActivityDefinitions.Services; +using Elsa.Common.Models; +using Elsa.JavaScript.Models; +using Elsa.JavaScript.Services; +using Elsa.Workflows.Core.Models; +using Elsa.Workflows.Runtime.Services; +using FastEndpoints; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.Api.Endpoints.Scripting.JavaScript.TypeDefinitions; + +/// +/// Returns a TypeScript definition that is used by the Monaco editor to display intellisense for JavaScript expressions. +/// +[PublicAPI] +internal class Get : Endpoint +{ + private readonly ITypeDefinitionService _typeDefinitionService; + private readonly IServiceProvider _serviceProvider; + + /// + public Get(ITypeDefinitionService typeDefinitionService, IServiceProvider serviceProvider) + { + _typeDefinitionService = typeDefinitionService; + _serviceProvider = serviceProvider; + } + + /// + public override void Configure() + { + Post("scripting/javascript/type-definitions/{containerId}"); + } + + /// + public override async Task HandleAsync(Request request, CancellationToken cancellationToken) + { + var variables = await GetVariables(request.ContainerType, request.ContainerId, cancellationToken); + var intellisenseContext = new TypeDefinitionContext(variables, request.ActivityTypeName, request.PropertyName, cancellationToken); + var typeDefinitions = await _typeDefinitionService.GenerateTypeDefinitionsAsync(intellisenseContext); + var fileName = $"elsa.{request.ContainerId}.d.ts"; + var data = Encoding.UTF8.GetBytes(typeDefinitions); + + await SendBytesAsync(data, fileName, "application/x-typescript", cancellation: cancellationToken); + } + + private async Task> GetVariables(string containerType, string containerId, CancellationToken cancellationToken) + { + // TODO: Make this extensible. + + switch (containerType) + { + case "workflow-definition": + { + var workflowDefinitionService = _serviceProvider.GetService(); + var workflowDefinition = workflowDefinitionService != null ? await workflowDefinitionService.FindAsync(containerId, VersionOptions.Latest, cancellationToken) : default; + var x = workflowDefinition; + return workflowDefinition?.Variables ?? new List(); + } + case "activity-definition": + { + var activityDefinitionStore = _serviceProvider.GetService(); + var activityDefinition = activityDefinitionStore != null ? await activityDefinitionStore.FindByDefinitionIdAsync(containerId, VersionOptions.Latest, cancellationToken) : default; + return activityDefinition?.Variables ?? new List(); + } + } + + return new List(); + } +} + +internal record Request(string ContainerType, string ContainerId, string? ActivityTypeName, string? PropertyName) +{ + public Request() : this(default!, default!, default, default) + { + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs index 4509bf6ea..cbd3ce44c 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs @@ -53,4 +53,13 @@ public static class VariableExtensions var genericType = variable.GetType().GenericTypeArguments.FirstOrDefault(); return genericType == null ? value : value.ConvertTo(genericType)!; } + + /// + /// Return the type of the variable. + /// + public static Type GetVariableType(this Variable variable) + { + var variableType = variable.GetType(); + return variableType.GenericTypeArguments.Any() ? variableType.GetGenericArguments().First() : typeof(object); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs index 873d149e5..e82cc58f4 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/VariableConverter.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Elsa.Expressions.Extensions; using Elsa.Expressions.Helpers; using Elsa.Expressions.Services; using Elsa.Extensions; diff --git a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs index 645527dae..5c65a5b46 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs @@ -1,3 +1,4 @@ +using Elsa.Expressions.Extensions; using Elsa.Expressions.Helpers; using Elsa.Expressions.Services; using Elsa.Extensions; diff --git a/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs b/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs index a585206e6..34f313e64 100644 --- a/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Management/Serialization/Converters/OutputJsonConverter.cs @@ -1,5 +1,6 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Elsa.Expressions.Extensions; using Elsa.Expressions.Services; using Elsa.Workflows.Core.Models; using Elsa.Workflows.Core.Services;