Incremental work on expression editor
This commit is contained in:
parent
fe2b4b75cf
commit
848d9d7b5d
|
|
@ -5,12 +5,12 @@ namespace Elsa.Expressions;
|
|||
|
||||
public class ElsaExpression : IExpression
|
||||
{
|
||||
public ElsaExpression(string scriptExpression)
|
||||
public ElsaExpression(string value)
|
||||
{
|
||||
ScriptExpression = scriptExpression;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string ScriptExpression { get; }
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
public class ElsaExpressionHandler : IExpressionHandler
|
||||
|
|
@ -20,6 +20,6 @@ public class ElsaExpressionHandler : IExpressionHandler
|
|||
var elsaExpression = (ElsaExpression)expression;
|
||||
|
||||
// TODO: evaluate expression.
|
||||
return new ValueTask<object?>(elsaExpression.ScriptExpression);
|
||||
return new ValueTask<object?>(elsaExpression.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class InputJsonConverter<T> : JsonConverter<Input<T>>
|
|||
if (!expressionElement.TryGetProperty("type", out var expressionTypeNameElement))
|
||||
throw new JsonException("Failed to extract expression type property");
|
||||
|
||||
var expressionTypeName = expressionTypeNameElement.GetString()!;
|
||||
var expressionTypeName = expressionTypeNameElement.GetString() ?? "Literal";
|
||||
var expressionSyntaxDescriptor = _expressionSyntaxRegistry.Find(expressionTypeName);
|
||||
|
||||
if (expressionSyntaxDescriptor == null)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
* It contains typing information for all components that exist in this project.
|
||||
*/
|
||||
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
||||
import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, SelectListItem, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models";
|
||||
import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Trigger, TriggerDescriptor, Workflow, WorkflowInstance, WorkflowInstanceSummary, WorkflowSummary } from "./models";
|
||||
import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./components/designer/activity-properties-editor/activity-properties-editor";
|
||||
import { AddActivityArgs } from "./components/designer/canvas/canvas";
|
||||
import { NodeInputContext } from "./services/node-input-driver";
|
||||
import { MenuItem } from "./components/shared/context-menu/models";
|
||||
import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models";
|
||||
import { AddActivityArgs as AddActivityArgs1 } from "./components/designer/canvas/canvas";
|
||||
import { ExpressionChangedArs } from "./components/designer/input-control-switch/input-control-switch";
|
||||
import { MonacoLib, MonacoValueChangedArgs } from "./components/shared/monaco-editor/monaco-editor";
|
||||
import { PagerData } from "./components/shared/pager/pager";
|
||||
import { PanelPosition, PanelStateChangedArgs } from "./components/designer/panel/models";
|
||||
|
|
@ -69,6 +70,19 @@ export namespace Components {
|
|||
"selectedTabIndex"?: number;
|
||||
"tabs": Array<TabDefinition>;
|
||||
}
|
||||
interface ElsaInputControlSwitch {
|
||||
"codeEditorHeight": string;
|
||||
"codeEditorSingleLineMode": boolean;
|
||||
"context"?: IntellisenseContext;
|
||||
"defaultSyntax": string;
|
||||
"expression"?: string;
|
||||
"fieldName"?: string;
|
||||
"hint": string;
|
||||
"isReadOnly"?: boolean;
|
||||
"label": string;
|
||||
"supportedSyntaxes": Array<string>;
|
||||
"syntax"?: string;
|
||||
}
|
||||
interface ElsaInputTags {
|
||||
"fieldId"?: string;
|
||||
"placeHolder"?: string;
|
||||
|
|
@ -93,7 +107,6 @@ export namespace Components {
|
|||
"monacoLibPath"?: string;
|
||||
"padding": string;
|
||||
"setJavaScriptLibs": (libs: Array<MonacoLib>) => Promise<void>;
|
||||
"setValue": (value: string) => Promise<void>;
|
||||
"singleLineMode": boolean;
|
||||
"value": string;
|
||||
}
|
||||
|
|
@ -240,6 +253,12 @@ declare global {
|
|||
prototype: HTMLElsaFormPanelElement;
|
||||
new (): HTMLElsaFormPanelElement;
|
||||
};
|
||||
interface HTMLElsaInputControlSwitchElement extends Components.ElsaInputControlSwitch, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaInputControlSwitchElement: {
|
||||
prototype: HTMLElsaInputControlSwitchElement;
|
||||
new (): HTMLElsaInputControlSwitchElement;
|
||||
};
|
||||
interface HTMLElsaInputTagsElement extends Components.ElsaInputTags, HTMLStencilElement {
|
||||
}
|
||||
var HTMLElsaInputTagsElement: {
|
||||
|
|
@ -394,6 +413,7 @@ declare global {
|
|||
"elsa-dropdown-input": HTMLElsaDropdownInputElement;
|
||||
"elsa-flowchart": HTMLElsaFlowchartElement;
|
||||
"elsa-form-panel": HTMLElsaFormPanelElement;
|
||||
"elsa-input-control-switch": HTMLElsaInputControlSwitchElement;
|
||||
"elsa-input-tags": HTMLElsaInputTagsElement;
|
||||
"elsa-input-tags-dropdown": HTMLElsaInputTagsDropdownElement;
|
||||
"elsa-modal-dialog": HTMLElsaModalDialogElement;
|
||||
|
|
@ -466,6 +486,21 @@ declare namespace LocalJSX {
|
|||
"selectedTabIndex"?: number;
|
||||
"tabs"?: Array<TabDefinition>;
|
||||
}
|
||||
interface ElsaInputControlSwitch {
|
||||
"codeEditorHeight"?: string;
|
||||
"codeEditorSingleLineMode"?: boolean;
|
||||
"context"?: IntellisenseContext;
|
||||
"defaultSyntax"?: string;
|
||||
"expression"?: string;
|
||||
"fieldName"?: string;
|
||||
"hint"?: string;
|
||||
"isReadOnly"?: boolean;
|
||||
"label"?: string;
|
||||
"onExpressionChanged"?: (event: CustomEvent<ExpressionChangedArs>) => void;
|
||||
"onSyntaxChanged"?: (event: CustomEvent<string>) => void;
|
||||
"supportedSyntaxes"?: Array<string>;
|
||||
"syntax"?: string;
|
||||
}
|
||||
interface ElsaInputTags {
|
||||
"fieldId"?: string;
|
||||
"onValueChanged"?: (event: CustomEvent<Array<string>>) => void;
|
||||
|
|
@ -596,6 +631,7 @@ declare namespace LocalJSX {
|
|||
"elsa-dropdown-input": ElsaDropdownInput;
|
||||
"elsa-flowchart": ElsaFlowchart;
|
||||
"elsa-form-panel": ElsaFormPanel;
|
||||
"elsa-input-control-switch": ElsaInputControlSwitch;
|
||||
"elsa-input-tags": ElsaInputTags;
|
||||
"elsa-input-tags-dropdown": ElsaInputTagsDropdown;
|
||||
"elsa-modal-dialog": ElsaModalDialog;
|
||||
|
|
@ -635,6 +671,7 @@ declare module "@stencil/core" {
|
|||
"elsa-dropdown-input": LocalJSX.ElsaDropdownInput & JSXBase.HTMLAttributes<HTMLElsaDropdownInputElement>;
|
||||
"elsa-flowchart": LocalJSX.ElsaFlowchart & JSXBase.HTMLAttributes<HTMLElsaFlowchartElement>;
|
||||
"elsa-form-panel": LocalJSX.ElsaFormPanel & JSXBase.HTMLAttributes<HTMLElsaFormPanelElement>;
|
||||
"elsa-input-control-switch": LocalJSX.ElsaInputControlSwitch & JSXBase.HTMLAttributes<HTMLElsaInputControlSwitchElement>;
|
||||
"elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes<HTMLElsaInputTagsElement>;
|
||||
"elsa-input-tags-dropdown": LocalJSX.ElsaInputTagsDropdown & JSXBase.HTMLAttributes<HTMLElsaInputTagsDropdownElement>;
|
||||
"elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes<HTMLElsaModalDialogElement>;
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export class ActivityPropertiesEditor {
|
|||
type: inputDescriptor.type,
|
||||
expression: {
|
||||
type: syntax,
|
||||
value: propertyValue
|
||||
value: propertyValue // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax.
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -146,7 +146,10 @@ export class ActivityPropertiesEditor {
|
|||
</FormEntry>
|
||||
|
||||
{properties.filter(x => !!x.inputControl).map(propertyContext => {
|
||||
return propertyContext.inputControl;
|
||||
const key = `${node.id}-${propertyContext.inputContext.inputDescriptor.name}`;
|
||||
return <div key={key}>
|
||||
{propertyContext.inputControl}
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
import {Component, Event, EventEmitter, Listen, h, Prop, State} from '@stencil/core';
|
||||
import {IntellisenseContext, SyntaxNames} from "../../../models";
|
||||
import {enter, leave, toggle} from 'el-transition'
|
||||
import {SyntaxSelectorIcon} from "../../icons/tooling/syntax-selector";
|
||||
import {MonacoValueChangedArgs} from "../../shared/monaco-editor/monaco-editor";
|
||||
import {Hint} from "../../shared/forms/hint";
|
||||
|
||||
export interface ExpressionChangedArs {
|
||||
expression: string;
|
||||
syntax: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-input-control-switch',
|
||||
shadow: false,
|
||||
})
|
||||
export class InputControlSwitch {
|
||||
private contextMenu: HTMLElement;
|
||||
private monacoEditor: HTMLElsaMonacoEditorElement;
|
||||
private defaultSyntaxValue: string;
|
||||
private contextMenuWidget: HTMLElement;
|
||||
|
||||
@Prop() label: string;
|
||||
@Prop() hint: string;
|
||||
@Prop() fieldName?: string;
|
||||
@Prop() syntax?: string;
|
||||
@Prop() expression?: string;
|
||||
@Prop() defaultSyntax: string = SyntaxNames.Literal;
|
||||
@Prop() supportedSyntaxes: Array<string> = [];
|
||||
@Prop() isReadOnly?: boolean;
|
||||
@Prop() codeEditorHeight: string = '10em';
|
||||
@Prop() codeEditorSingleLineMode: boolean = false;
|
||||
@Prop() context?: IntellisenseContext;
|
||||
|
||||
@Event() syntaxChanged: EventEmitter<string>;
|
||||
@Event() expressionChanged: EventEmitter<ExpressionChangedArs>;
|
||||
|
||||
@State() currentExpression?: string;
|
||||
|
||||
@Listen('click', {target: 'window'})
|
||||
private onWindowClicked(event: Event) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
if (!this.contextMenuWidget || !this.contextMenuWidget.contains(target))
|
||||
this.closeContextMenu();
|
||||
}
|
||||
|
||||
public componentWillLoad() {
|
||||
this.currentExpression = this.expression;
|
||||
}
|
||||
|
||||
public render() {
|
||||
return <div class="p-4">
|
||||
<div class="mb-1">
|
||||
<div class="flex">
|
||||
<div class="flex-1">
|
||||
{this.renderLabel()}
|
||||
</div>
|
||||
{this.renderContextMenuWidget()}
|
||||
</div>
|
||||
</div>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
}
|
||||
|
||||
private renderLabel() {
|
||||
if (!this.label)
|
||||
return undefined;
|
||||
|
||||
const fieldId = this.fieldName;
|
||||
const fieldLabel = this.label || fieldId;
|
||||
|
||||
return <label htmlFor={fieldId} class="block text-sm font-medium text-gray-700">
|
||||
{fieldLabel}
|
||||
</label>;
|
||||
}
|
||||
|
||||
private renderContextMenuWidget() {
|
||||
if (this.supportedSyntaxes.length == 0)
|
||||
return undefined;
|
||||
|
||||
const selectedSyntax = this.syntax;
|
||||
const advancedButtonClass = selectedSyntax ? 'text-blue-500' : 'text-gray-300'
|
||||
|
||||
return <div class="relative" ref={el => this.contextMenuWidget = el}>
|
||||
<button type="button" class={`border-0 focus:outline-none text-sm ${advancedButtonClass}`} onClick={e => this.onSettingsClick(e)}>
|
||||
{!this.isReadOnly ? this.renderContextMenuButton() : ""}
|
||||
</button>
|
||||
<div>
|
||||
<div ref={el => this.contextMenu = el}
|
||||
data-transition-enter="transition ease-out duration-100"
|
||||
data-transition-enter-start="transform opacity-0 scale-95"
|
||||
data-transition-enter-end="transform opacity-100 scale-100"
|
||||
data-transition-leave="transition ease-in duration-75"
|
||||
data-transition-leave-start="transform opacity-100 scale-100"
|
||||
data-transition-leave-end="transform opacity-0 scale-95"
|
||||
class="hidden origin-top-right absolute right-1 mt-1 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none z-10" role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="options-menu">
|
||||
<div class="py-1" role="none">
|
||||
<a onClick={e => this.selectSyntax(e, null)} href="#" class={`block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 ${!selectedSyntax ? 'text-blue-700' : 'text-gray-700'}`} role="menuitem">Default</a>
|
||||
</div>
|
||||
<div class="py-1" role="none">
|
||||
{this.supportedSyntaxes.map(syntax => (
|
||||
<a onClick={e => this.selectSyntax(e, syntax)} href="#"
|
||||
class={`block px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 ${selectedSyntax == syntax ? 'text-blue-700' : 'text-gray-700'}`} role="menuitem">{syntax}</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderContextMenuButton = () => this.syntax ? <span>{this.syntax}</span> : <SyntaxSelectorIcon/>;
|
||||
|
||||
private renderEditor = () => {
|
||||
const selectedSyntax = this.syntax;
|
||||
const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax);
|
||||
const value = this.expression;
|
||||
const expressionEditorClass = selectedSyntax ? 'block' : 'hidden';
|
||||
const defaultEditorClass = selectedSyntax ? 'hidden' : 'block';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div class={expressionEditorClass}>
|
||||
<elsa-monaco-editor
|
||||
value={value}
|
||||
language={monacoLanguage}
|
||||
editor-height={this.codeEditorHeight}
|
||||
single-line={this.codeEditorSingleLineMode}
|
||||
onValueChanged={e => this.onExpressionChanged(e.detail)}
|
||||
ref={el => this.monacoEditor = el}/>
|
||||
</div>
|
||||
<div class={defaultEditorClass}>
|
||||
<slot/>
|
||||
</div>
|
||||
<Hint text={this.hint}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private mapSyntaxToLanguage = (syntax: string): string => {
|
||||
switch (syntax) {
|
||||
case 'Json':
|
||||
return 'json';
|
||||
case 'JavaScript':
|
||||
return 'javascript';
|
||||
case 'Liquid':
|
||||
return 'handlebars';
|
||||
case 'Literal':
|
||||
default:
|
||||
return 'plaintext';
|
||||
}
|
||||
};
|
||||
|
||||
private toggleContextMenu() {
|
||||
toggle(this.contextMenu);
|
||||
}
|
||||
|
||||
private openContextMenu() {
|
||||
enter(this.contextMenu);
|
||||
}
|
||||
|
||||
private closeContextMenu() {
|
||||
if (!!this.contextMenu)
|
||||
leave(this.contextMenu);
|
||||
}
|
||||
|
||||
private selectDefaultEditor(e: Event) {
|
||||
e.preventDefault();
|
||||
this.syntax = undefined;
|
||||
this.closeContextMenu();
|
||||
}
|
||||
|
||||
private async selectSyntax(e: Event, syntax: string) {
|
||||
e.preventDefault();
|
||||
|
||||
this.syntax = syntax;
|
||||
this.syntaxChanged.emit(syntax);
|
||||
this.expressionChanged.emit({expression: this.currentExpression, syntax});
|
||||
|
||||
this.closeContextMenu();
|
||||
}
|
||||
|
||||
private onSettingsClick(e: Event) {
|
||||
this.toggleContextMenu();
|
||||
}
|
||||
|
||||
private onExpressionChanged(e: MonacoValueChangedArgs) {
|
||||
const expression = e.value;
|
||||
this.currentExpression = expression;
|
||||
this.expressionChanged.emit({expression, syntax: this.syntax});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import {FunctionalComponent, h} from "@stencil/core";
|
||||
|
||||
export const SyntaxSelectorIcon: FunctionalComponent = () =>
|
||||
<svg class="h-5 w-5 text-gray-400" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<line x1="8" y1="12" x2="8" y2="12.01"/>
|
||||
<line x1="12" y1="12" x2="12" y2="12.01"/>
|
||||
<line x1="16" y1="12" x2="16" y2="12.01"/>
|
||||
</svg>;
|
||||
|
|
@ -1,27 +1,41 @@
|
|||
import {Component, Prop, h} from '@stencil/core';
|
||||
import {Component, h, Prop} from '@stencil/core';
|
||||
import {LiteralExpression, SyntaxNames} from "../../models";
|
||||
import {NodeInputContext} from "../../services/node-input-driver";
|
||||
import {getInputPropertyValue} from "../../utils";
|
||||
import {ExpressionChangedArs} from "../designer/input-control-switch/input-control-switch";
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-single-line-input',
|
||||
shadow: false
|
||||
})
|
||||
export class SingleLineInput {
|
||||
@Prop() inputContext: NodeInputContext;
|
||||
@Prop() public inputContext: NodeInputContext;
|
||||
|
||||
public render() {
|
||||
const inputContext = this.inputContext;
|
||||
const inputProperty = inputContext.inputDescriptor;
|
||||
const fieldName = inputProperty.name;
|
||||
const fieldId = inputProperty.name;
|
||||
const inputDescriptor = inputContext.inputDescriptor;
|
||||
const fieldName = inputDescriptor.name;
|
||||
const fieldId = inputDescriptor.name;
|
||||
const displayName = inputDescriptor.displayName;
|
||||
const hint = inputDescriptor.description;
|
||||
const input = getInputPropertyValue(inputContext);
|
||||
const value = (input?.expression as LiteralExpression)?.value;
|
||||
return <input type="text" name={fieldName} id={fieldId} value={value} onChange={this.onPropertyEditorChanged}/>
|
||||
const value = (input?.expression as LiteralExpression)?.value; // TODO: The "value" field is currently hardcoded, but we should be able to be more flexible and potentially have different fields for a given syntax.
|
||||
const syntax = input?.expression?.type ?? inputDescriptor.defaultSyntax;
|
||||
const supportedSyntaxes = ['Literal', 'JavaScript', 'Liquid'];
|
||||
|
||||
return (
|
||||
<elsa-input-control-switch label={displayName} hint={hint} supportedSyntaxes={supportedSyntaxes} syntax={syntax} expression={value} onExpressionChanged={this.onExpressionChanged}>
|
||||
<input type="text" name={fieldName} id={fieldId} value={value} onChange={this.onPropertyEditorChanged}/>
|
||||
</elsa-input-control-switch>
|
||||
);
|
||||
}
|
||||
|
||||
private onPropertyEditorChanged = (e: Event) => {
|
||||
const inputElement = e.target as HTMLInputElement;
|
||||
this.inputContext.inputChanged(inputElement.value, SyntaxNames.Literal);
|
||||
}
|
||||
|
||||
private onExpressionChanged = (e: CustomEvent<ExpressionChangedArs>) => {
|
||||
this.inputContext.inputChanged(e.detail.expression, e.detail.syntax);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,45 +39,45 @@ export class MonacoEditor {
|
|||
this.monaco.editor.setModelLanguage(model, this.language);
|
||||
}
|
||||
|
||||
@Method()
|
||||
async setValue(value: string) {
|
||||
@Watch('value')
|
||||
valueChangeHandler(newValue: string) {
|
||||
if (!this.editor)
|
||||
return;
|
||||
|
||||
const model = this.editor.getModel();
|
||||
model.setValue(value || '');
|
||||
model.setValue(newValue || '');
|
||||
}
|
||||
|
||||
@Method()
|
||||
async setJavaScriptLibs(libs: Array<MonacoLib>) {
|
||||
const monaco = this.monaco;
|
||||
const editor = this.editor;
|
||||
|
||||
const defaultLib: MonacoLib = {
|
||||
content: "<reference lib=\"es5\" />",
|
||||
filePath: "lib.d.ts"
|
||||
};
|
||||
|
||||
libs = [defaultLib, ...libs];
|
||||
libs = [...libs];
|
||||
monaco.languages.typescript.javascriptDefaults.setExtraLibs(libs);
|
||||
|
||||
for (const lib of libs) {
|
||||
const oldModel = monaco.editor.getModel(lib.filePath);
|
||||
|
||||
const oldModel = editor.getModel(lib.filePath);
|
||||
|
||||
if (oldModel)
|
||||
oldModel.dispose();
|
||||
|
||||
monaco.editor.createModel(lib.content, 'typescript', monaco.Uri.parse(lib.filePath));
|
||||
const newModel = monaco.editor.createModel(lib.content, 'typescript', monaco.Uri.parse(lib.filePath));
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillLoad() {
|
||||
public async componentWillLoad() {
|
||||
const monacoLibPath = this.monacoLibPath || globalState.monacoLibPath;
|
||||
this.monaco = await initializeMonacoWorker(monacoLibPath);
|
||||
await this.setJavaScriptLibs([]);
|
||||
this.registerLiquid();
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
public async componentDidLoad() {
|
||||
const monaco = this.monaco;
|
||||
const language = this.language;
|
||||
|
||||
|
|
@ -135,6 +135,9 @@ export class MonacoEditor {
|
|||
|
||||
this.editor = monaco.editor.create(this.container, options);
|
||||
|
||||
await this.setJavaScriptLibs([]);
|
||||
this.registerLiquid();
|
||||
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
const value = this.editor.getValue();
|
||||
const markers = monaco.editor.getModelMarkers({owner: language});
|
||||
|
|
@ -207,7 +210,7 @@ export class MonacoEditor {
|
|||
}
|
||||
|
||||
render() {
|
||||
const padding = this.padding || 'elsa-pt-1.5 elsa-pl-1';
|
||||
const padding = this.padding || 'pt-1.5 pl-1';
|
||||
return (
|
||||
<Host
|
||||
class="monaco-editor-host border focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300 p-4"
|
||||
|
|
|
|||
|
|
@ -20,17 +20,7 @@ export class DefaultInputDriver implements NodeInputDriver {
|
|||
const inputDescriptor = context.inputDescriptor;
|
||||
const uiHint = inputDescriptor.uiHint;
|
||||
const inputControl = this.inputControlRegistry.get(uiHint);
|
||||
const node = context.node;
|
||||
const propertyName = inputDescriptor.name;
|
||||
const displayName = inputDescriptor.displayName || propertyName;
|
||||
const description = inputDescriptor.description;
|
||||
const fieldId = inputDescriptor.name;
|
||||
const key = `${node.id}_${propertyName}`;
|
||||
|
||||
return (
|
||||
<FormEntry label={displayName} fieldId={fieldId} hint={description} key={key}>
|
||||
{inputControl(context)}
|
||||
</FormEntry>);
|
||||
return inputControl(context);
|
||||
}
|
||||
|
||||
supportsInput(context: NodeInputContext): boolean {
|
||||
|
|
|
|||
|
|
@ -121,3 +121,8 @@ export interface VersionOptions {
|
|||
allVersions?: boolean;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
export interface IntellisenseContext {
|
||||
activityTypeName: string;
|
||||
propertyName: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export interface LiteralExpression {
|
|||
|
||||
export interface JavaScriptExpression {
|
||||
type: ExpressionType;
|
||||
scriptExpression: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface JsonExpression {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Elsa.Pipelines.WorkflowExecution.Components;
|
|||
using Elsa.Runtime.ProtoActor.Extensions;
|
||||
using Elsa.Samples.Web1.Activities;
|
||||
using Elsa.Samples.Web1.Workflows;
|
||||
using Elsa.Scripting.JavaScript;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
|
@ -27,7 +28,6 @@ var services = builder.Services;
|
|||
services
|
||||
.AddElsa()
|
||||
.AddMediator()
|
||||
//.AddInMemoryPersistence()
|
||||
.AddEntityFrameworkCorePersistence((_, ef) => ef.UseSqlite())
|
||||
.IndexWorkflowTriggers()
|
||||
.AddElsaManagement()
|
||||
|
|
@ -57,6 +57,9 @@ services
|
|||
services
|
||||
.AddTrigger<HttpTrigger>();
|
||||
|
||||
// Register scripting languages.
|
||||
services.AddJavaScriptExpressions();
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
var serviceProvider = app.Services;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
<ProjectReference Include="..\..\core\Elsa.Management\Elsa.Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ namespace Elsa.Scripting.JavaScript;
|
|||
|
||||
public class JavaScriptExpression : IExpression
|
||||
{
|
||||
public JavaScriptExpression(string scriptExpression)
|
||||
public JavaScriptExpression(string value)
|
||||
{
|
||||
ScriptExpression = scriptExpression;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string ScriptExpression { get; }
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
public class JavaScriptExpressionHandler : IExpressionHandler
|
||||
|
|
@ -22,6 +22,6 @@ public class JavaScriptExpressionHandler : IExpressionHandler
|
|||
var javaScriptExpression = (JavaScriptExpression)expression;
|
||||
|
||||
// TODO: evaluate expression.
|
||||
return new ValueTask<object?>(javaScriptExpression.ScriptExpression);
|
||||
return new ValueTask<object?>(javaScriptExpression.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Contracts;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Management.Contracts;
|
||||
using Elsa.Management.Models;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Scripting.JavaScript;
|
||||
|
||||
public class JavaScriptExpressionSyntaxProvider : IExpressionSyntaxProvider
|
||||
{
|
||||
private readonly IIdentityGenerator _identityGenerator;
|
||||
|
||||
public JavaScriptExpressionSyntaxProvider(IIdentityGenerator identityGenerator)
|
||||
{
|
||||
_identityGenerator = identityGenerator;
|
||||
}
|
||||
|
||||
public ValueTask<IEnumerable<ExpressionSyntaxDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var javaScript = CreateJavaScriptDescriptor();
|
||||
|
||||
return ValueTask.FromResult<IEnumerable<ExpressionSyntaxDescriptor>>(new[] { javaScript });
|
||||
}
|
||||
|
||||
private ExpressionSyntaxDescriptor CreateJavaScriptDescriptor() => CreateDescriptor<JavaScriptExpression>(
|
||||
"JavaScript",
|
||||
CreateJavaScriptExpression,
|
||||
context => new JavaScriptExpressionReference(context.GetExpression<JavaScriptExpression>()),
|
||||
expression => expression.Value);
|
||||
|
||||
private ExpressionSyntaxDescriptor CreateDescriptor<TExpression>(
|
||||
string syntax,
|
||||
Func<ExpressionConstructorContext, IExpression> constructor,
|
||||
Func<LocationReferenceConstructorContext, RegisterLocationReference> createLocationReference,
|
||||
Func<TExpression, object?> expressionValue) =>
|
||||
new()
|
||||
{
|
||||
Syntax = syntax,
|
||||
Type = typeof(TExpression),
|
||||
CreateExpression = constructor,
|
||||
CreateLocationReference = context =>
|
||||
{
|
||||
var reference = createLocationReference(context);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(reference.Id))
|
||||
reference.Id = GenerateId();
|
||||
|
||||
return reference;
|
||||
},
|
||||
CreateSerializableObject = context => new
|
||||
{
|
||||
Type = syntax,
|
||||
Value = expressionValue(context.GetExpression<TExpression>())
|
||||
}
|
||||
};
|
||||
|
||||
private IExpression CreateJavaScriptExpression(ExpressionConstructorContext context)
|
||||
{
|
||||
var script = context.Element.GetProperty("value").ToString();
|
||||
return new JavaScriptExpression(script);
|
||||
}
|
||||
|
||||
private string GenerateId() => _identityGenerator.GenerateId();
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Management.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Scripting.JavaScript;
|
||||
|
|
@ -7,6 +8,8 @@ public static class ServiceCollectionExtensions
|
|||
{
|
||||
public static IServiceCollection AddJavaScriptExpressions(this IServiceCollection services)
|
||||
{
|
||||
return services.AddExpressionHandler<JavaScriptExpressionHandler, JavaScriptExpression>();
|
||||
return services
|
||||
.AddSingleton<IExpressionSyntaxProvider, JavaScriptExpressionSyntaxProvider>()
|
||||
.AddExpressionHandler<JavaScriptExpressionHandler, JavaScriptExpression>();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue