Incremental work on property editor modes

This commit is contained in:
Sipke Schoorstra 2021-04-02 22:01:06 +02:00
parent 8413cb0e55
commit 3bca56f0ff
14 changed files with 453289 additions and 364 deletions

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,10 @@
"@stencil/postcss": "^2.0.0",
"@stencil/state-tunnel": "^1.0.1",
"@stencil/store": "^1.4.1",
"@tailwindcss/aspect-ratio": "^0.2.0",
"@tailwindcss/forms": "^0.3.2",
"@tailwindcss/jit": "^0.1.18",
"@tailwindcss/typography": "^0.4.0",
"autoprefixer": "10.2.5",
"axios": "^0.21.1",
"cssnano": "^4.1.10",

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,17 @@ export namespace Components {
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
}
interface ElsaExpressionEditor {
"context"?: string;
"editorHeight": string;
"expression": string;
"fieldName": string;
"serverUrl": string;
"singleLineMode": boolean;
"syntax": string;
"syntaxes"?: Array<string>;
"workflowDefinitionId": string;
}
interface ElsaInputTags {
"fieldId"?: string;
"fieldName"?: string;
@ -54,6 +65,13 @@ export namespace Components {
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
}
interface ElsaPropertyEditor {
"context"?: string;
"editorHeight": string;
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
"singleLineMode": boolean;
}
interface ElsaScriptProperty {
"context"?: string;
"editorHeight": string;
@ -65,13 +83,8 @@ export namespace Components {
"workflowDefinitionId": string;
}
interface ElsaTextProperty {
"context"?: string;
"editorHeight": string;
"propertyDescriptor": ActivityPropertyDescriptor;
"propertyModel": ActivityDefinitionProperty;
"serverUrl": string;
"singleLineMode": boolean;
"workflowDefinitionId": string;
}
interface ElsaToastNotification {
"hide": () => Promise<void>;
@ -139,6 +152,12 @@ declare global {
prototype: HTMLElsaDropdownPropertyElement;
new (): HTMLElsaDropdownPropertyElement;
};
interface HTMLElsaExpressionEditorElement extends Components.ElsaExpressionEditor, HTMLStencilElement {
}
var HTMLElsaExpressionEditorElement: {
prototype: HTMLElsaExpressionEditorElement;
new (): HTMLElsaExpressionEditorElement;
};
interface HTMLElsaInputTagsElement extends Components.ElsaInputTags, HTMLStencilElement {
}
var HTMLElsaInputTagsElement: {
@ -163,6 +182,12 @@ declare global {
prototype: HTMLElsaMultiTextPropertyElement;
new (): HTMLElsaMultiTextPropertyElement;
};
interface HTMLElsaPropertyEditorElement extends Components.ElsaPropertyEditor, HTMLStencilElement {
}
var HTMLElsaPropertyEditorElement: {
prototype: HTMLElsaPropertyEditorElement;
new (): HTMLElsaPropertyEditorElement;
};
interface HTMLElsaScriptPropertyElement extends Components.ElsaScriptProperty, HTMLStencilElement {
}
var HTMLElsaScriptPropertyElement: {
@ -213,10 +238,12 @@ declare global {
"elsa-designer-tree": HTMLElsaDesignerTreeElement;
"elsa-designer-tree-activity": HTMLElsaDesignerTreeActivityElement;
"elsa-dropdown-property": HTMLElsaDropdownPropertyElement;
"elsa-expression-editor": HTMLElsaExpressionEditorElement;
"elsa-input-tags": HTMLElsaInputTagsElement;
"elsa-modal-dialog": HTMLElsaModalDialogElement;
"elsa-monaco": HTMLElsaMonacoElement;
"elsa-multi-text-property": HTMLElsaMultiTextPropertyElement;
"elsa-property-editor": HTMLElsaPropertyEditorElement;
"elsa-script-property": HTMLElsaScriptPropertyElement;
"elsa-text-property": HTMLElsaTextPropertyElement;
"elsa-toast-notification": HTMLElsaToastNotificationElement;
@ -253,6 +280,17 @@ declare namespace LocalJSX {
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
}
interface ElsaExpressionEditor {
"context"?: string;
"editorHeight"?: string;
"expression"?: string;
"fieldName"?: string;
"serverUrl"?: string;
"singleLineMode"?: boolean;
"syntax"?: string;
"syntaxes"?: Array<string>;
"workflowDefinitionId"?: string;
}
interface ElsaInputTags {
"fieldId"?: string;
"fieldName"?: string;
@ -273,6 +311,13 @@ declare namespace LocalJSX {
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
}
interface ElsaPropertyEditor {
"context"?: string;
"editorHeight"?: string;
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
"singleLineMode"?: boolean;
}
interface ElsaScriptProperty {
"context"?: string;
"editorHeight"?: string;
@ -284,13 +329,8 @@ declare namespace LocalJSX {
"workflowDefinitionId"?: string;
}
interface ElsaTextProperty {
"context"?: string;
"editorHeight"?: string;
"propertyDescriptor"?: ActivityPropertyDescriptor;
"propertyModel"?: ActivityDefinitionProperty;
"serverUrl"?: string;
"singleLineMode"?: boolean;
"workflowDefinitionId"?: string;
}
interface ElsaToastNotification {
}
@ -320,10 +360,12 @@ declare namespace LocalJSX {
"elsa-designer-tree": ElsaDesignerTree;
"elsa-designer-tree-activity": ElsaDesignerTreeActivity;
"elsa-dropdown-property": ElsaDropdownProperty;
"elsa-expression-editor": ElsaExpressionEditor;
"elsa-input-tags": ElsaInputTags;
"elsa-modal-dialog": ElsaModalDialog;
"elsa-monaco": ElsaMonaco;
"elsa-multi-text-property": ElsaMultiTextProperty;
"elsa-property-editor": ElsaPropertyEditor;
"elsa-script-property": ElsaScriptProperty;
"elsa-text-property": ElsaTextProperty;
"elsa-toast-notification": ElsaToastNotification;
@ -344,10 +386,12 @@ declare module "@stencil/core" {
"elsa-designer-tree": LocalJSX.ElsaDesignerTree & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeElement>;
"elsa-designer-tree-activity": LocalJSX.ElsaDesignerTreeActivity & JSXBase.HTMLAttributes<HTMLElsaDesignerTreeActivityElement>;
"elsa-dropdown-property": LocalJSX.ElsaDropdownProperty & JSXBase.HTMLAttributes<HTMLElsaDropdownPropertyElement>;
"elsa-expression-editor": LocalJSX.ElsaExpressionEditor & JSXBase.HTMLAttributes<HTMLElsaExpressionEditorElement>;
"elsa-input-tags": LocalJSX.ElsaInputTags & JSXBase.HTMLAttributes<HTMLElsaInputTagsElement>;
"elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes<HTMLElsaModalDialogElement>;
"elsa-monaco": LocalJSX.ElsaMonaco & JSXBase.HTMLAttributes<HTMLElsaMonacoElement>;
"elsa-multi-text-property": LocalJSX.ElsaMultiTextProperty & JSXBase.HTMLAttributes<HTMLElsaMultiTextPropertyElement>;
"elsa-property-editor": LocalJSX.ElsaPropertyEditor & JSXBase.HTMLAttributes<HTMLElsaPropertyEditorElement>;
"elsa-script-property": LocalJSX.ElsaScriptProperty & JSXBase.HTMLAttributes<HTMLElsaScriptPropertyElement>;
"elsa-text-property": LocalJSX.ElsaTextProperty & JSXBase.HTMLAttributes<HTMLElsaTextPropertyElement>;
"elsa-toast-notification": LocalJSX.ElsaToastNotification & JSXBase.HTMLAttributes<HTMLElsaToastNotificationElement>;

View file

@ -0,0 +1,99 @@
import {Component, h, Host, Prop, State, Watch} from '@stencil/core';
import {createElsaClient} from "../../../../services/elsa-client";
import Tunnel from '../../../data/workflow-editor';
import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco";
@Component({
tag: 'elsa-expression-editor',
styleUrl: 'elsa-expression-editor.css',
shadow: false,
})
export class ElsaExpressionEditor {
@Prop() fieldName: string;
@Prop() syntax: string;
@Prop() expression: string;
@Prop() syntaxes?: Array<string>;
@Prop({attribute: 'editor-height', reflect: true}) editorHeight: string = '6em';
@Prop({attribute: 'single-line', reflect: true}) singleLineMode: boolean = false;
@Prop({attribute: 'context', reflect: true}) context?: string;
@Prop({mutable: true}) serverUrl: string;
@Prop({mutable: true}) workflowDefinitionId: string;
@State() selectedSyntax?: string;
@State() currentExpression?: string
monacoEditor: HTMLElsaMonacoElement;
@Watch("syntax")
syntaxChangedHandler(newValue: string) {
this.selectedSyntax = newValue;
}
@Watch("expression")
expressionChangedHandler(newValue: string) {
this.currentExpression = newValue;
}
async componentWillLoad() {
this.selectedSyntax = this.syntax;
this.currentExpression = this.expression;
}
async componentDidLoad() {
const elsaClient = createElsaClient(this.serverUrl);
const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context);
const libUri = 'defaultLib:lib.es6.d.ts';
await this.monacoEditor.addJavaScriptLib(libSource, libUri);
}
mapSyntaxToLanguage(syntax: string): any {
switch (syntax) {
case 'JavaScript':
return 'javascript';
case 'Liquid':
return 'handlebars';
case 'Literal':
default:
return 'plaintext';
}
}
onSyntaxListChange(e: Event) {
this.selectedSyntax = (e.currentTarget as HTMLSelectElement).value;
}
onSyntaxSelect(e: Event, syntax: string) {
e.preventDefault()
this.selectedSyntax = syntax;
}
onMonacoValueChanged(e: MonacoValueChangedArgs) {
this.currentExpression = e.value;
}
render() {
const syntaxes = this.syntaxes || ['Literal', 'JavaScript', 'Liquid'];
const selectedSyntax = this.selectedSyntax ?? (syntaxes.length > 0 ? syntaxes[0] : '');
const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax);
const fieldName = this.fieldName;
const syntaxFieldName = `${fieldName}Syntax`;
const value = this.currentExpression;
const reversedSyntaxes = [...syntaxes];
return (
<Host>
<div class="mt-1">
<elsa-monaco value={value}
language={monacoLanguage}
editor-height={this.editorHeight}
single-line={this.singleLineMode}
onValueChanged={e => this.onMonacoValueChanged(e.detail)}
ref={el => this.monacoEditor = el}/>
</div>
<input type="hidden" name={fieldName} value={value}/>
<input type="hidden" name={syntaxFieldName} value={selectedSyntax}/>
</Host>
)
}
}
Tunnel.injectProps(ElsaExpressionEditor, ['serverUrl', 'workflowDefinitionId']);

View file

@ -0,0 +1,113 @@
import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor} from "../../../../models";
@Component({
tag: 'elsa-property-editor',
styleUrl: 'elsa-property-editor.css',
shadow: false,
})
export class ElsaPropertyEditor {
@Prop() propertyDescriptor: ActivityPropertyDescriptor;
@Prop() propertyModel: ActivityDefinitionProperty;
@Prop({attribute: 'editor-height', reflect: true}) editorHeight: string = '6em';
@Prop({attribute: 'single-line', reflect: true}) singleLineMode: boolean = false;
@Prop({attribute: 'context', reflect: true}) context?: string;
@State() selectedSyntax?: string;
@State() currentValue?: string
@State() advancedMode: boolean
async componentWillLoad() {
this.selectedSyntax = this.propertyModel.syntax;
let currentValue = this.propertyModel.expression;
if (currentValue == undefined) {
const defaultValue = this.propertyDescriptor.defaultValue;
currentValue = defaultValue ? defaultValue.toString() : undefined;
}
this.currentValue = currentValue;
}
onAdvancedModeClick(e: Event) {
this.advancedMode = !this.advancedMode;
}
render() {
const propertyDescriptor = this.propertyDescriptor;
const propertyName = propertyDescriptor.name;
const fieldId = propertyName;
const fieldLabel = propertyDescriptor.label || propertyName;
const fieldHint = propertyDescriptor.hint;
const advancedButtonClass = this.advancedMode ? 'text-blue-500' : 'text-gray-300'
return <div>
<div class="mb-1">
<div class="flex">
<div class="flex-1">
<label htmlFor={fieldId} class="block text-sm font-medium text-gray-700">
{fieldLabel}
</label>
</div>
<div class="relative" x-data="{ open: true }">
<div x-description="Dropdown menu, show/hide based on menu state." x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95"
class="origin-top-right absolute right-1 mt-2 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 href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Text</a>
</div>
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Literal</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">JavaScript</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Liquid</a>
</div>
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Output</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Variable</a>
</div>
</div>
</div>
<div>
<button type="button" class={`border-0 focus:outline-none ${advancedButtonClass}`} onClick={e => this.onAdvancedModeClick(e)}>
<svg class="h-5 w-5" 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"/>
<polyline points="7 8 3 12 7 16"/>
<polyline points="17 8 21 12 17 16"/>
<line x1="14" y1="4" x2="10" y2="20"/>
</svg>
</button>
</div>
</div>
</div>
{this.renderEditor()}
{fieldHint ? <p class="mt-2 text-sm text-gray-500">{fieldHint}</p> : undefined}
</div>
}
renderEditor() {
const propertyDescriptor = this.propertyDescriptor;
const options = propertyDescriptor.options || {};
const syntaxes = options.syntaxes || ['Literal', 'JavaScript', 'Liquid'];
const selectedSyntax = this.selectedSyntax ?? (syntaxes.length > 0 ? syntaxes[0] : '');
const fieldName = propertyDescriptor.name;
const value = this.currentValue;
const advancedMode = this.advancedMode;
const expressionEditorClass = advancedMode ? 'block' : 'hidden';
const defaultEditorClass = advancedMode ? 'hidden' : 'block';
return (
[
<div class={expressionEditorClass}>
<elsa-expression-editor fieldName={fieldName} expression={value} syntaxes={syntaxes} syntax={selectedSyntax} editorHeight={this.editorHeight} singleLineMode={this.singleLineMode} context={this.context}/>
</div>,
<div class={defaultEditorClass}>
<slot/>
</div>
]
);
}
}

View file

@ -1,7 +1,5 @@
import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor} from "../../../../models";
import {createElsaClient} from "../../../../services/elsa-client";
import Tunnel from '../../../data/workflow-editor';
import {MonacoValueChangedArgs} from "../../monaco/elsa-monaco/elsa-monaco";
@Component({
@ -13,17 +11,9 @@ export class ElsaTextProperty {
@Prop() propertyDescriptor: ActivityPropertyDescriptor;
@Prop() propertyModel: ActivityDefinitionProperty;
@Prop({attribute: 'editor-height', reflect: true}) editorHeight: string = '6em';
@Prop({attribute: 'single-line', reflect: true}) singleLineMode: boolean = false;
@Prop({attribute: 'context', reflect: true}) context?: string;
@Prop({mutable: true}) serverUrl: string;
@Prop({mutable: true}) workflowDefinitionId: string;
@State() selectedSyntax?: string;
@State() currentValue?: string
monacoEditor: HTMLElsaMonacoElement;
async componentWillLoad() {
this.selectedSyntax = this.propertyModel.syntax;
let currentValue = this.propertyModel.expression;
@ -35,99 +25,19 @@ export class ElsaTextProperty {
this.currentValue = currentValue;
}
async componentDidLoad() {
const elsaClient = createElsaClient(this.serverUrl);
const libSource = await elsaClient.scriptingApi.getJavaScriptTypeDefinitions(this.workflowDefinitionId, this.context);
const libUri = 'defaultLib:lib.es6.d.ts';
await this.monacoEditor.addJavaScriptLib(libSource, libUri);
}
mapSyntaxToLanguage(syntax: string): any {
switch (syntax) {
case 'JavaScript':
return 'javascript';
case 'Liquid':
return 'handlebars';
case 'Literal':
default:
return 'plaintext';
}
}
onSyntaxListChange(e: Event) {
this.selectedSyntax = (e.currentTarget as HTMLSelectElement).value;
}
onSyntaxSelect(e: Event, syntax: string) {
e.preventDefault()
this.selectedSyntax = syntax;
}
onMonacoValueChanged(e: MonacoValueChangedArgs) {
this.currentValue = e.value;
}
render() {
const propertyDescriptor = this.propertyDescriptor;
const options = propertyDescriptor.options || {};
const syntaxes = options.syntaxes || ['Literal', 'JavaScript', 'Liquid'];
const selectedSyntax = this.selectedSyntax ?? (syntaxes.length > 0 ? syntaxes[0] : '');
const monacoLanguage = this.mapSyntaxToLanguage(selectedSyntax);
const propertyName = propertyDescriptor.name;
const fieldId = propertyName;
const fieldName = propertyName;
const fieldLabel = propertyDescriptor.label || propertyName;
const fieldHint = propertyDescriptor.hint;
const syntaxFieldName = `${fieldName}Syntax`;
const value = this.currentValue;
const reversedSyntaxes = [...syntaxes].reverse();
return <div>
<div class="flex">
<div class="">
<label htmlFor={fieldId} class="block text-sm font-medium text-gray-700">
{fieldLabel}
</label>
</div>
<div class="flex-1">
<div class="">
<div class="sm:hidden">
<label htmlFor="tabs" class="sr-only">Select a tab</label>
<select id="tabs" name="tabs" onChange={e => this.onSyntaxListChange(e)} class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
{reversedSyntaxes.map(syntax => {
return <option selected={syntax == selectedSyntax}>{syntax}</option>;
})}
</select>
</div>
<div class="hidden sm:block">
<nav class="flex flex-row-reverse" aria-label="Tabs">
{reversedSyntaxes.map(syntax => {
const isSelected = syntax == selectedSyntax;
const className = isSelected ? 'bg-blue-100 text-blue-700' : 'text-gray-500 hover:text-gray-700';
return <a href="#" onClick={e => this.onSyntaxSelect(e, syntax)} class={`${className} px-3 py-2 font-medium text-sm rounded-md`}>
{syntax}
</a>;
})}
</nav>
</div>
</div>
</div>
</div>
<div class="mt-1">
<elsa-monaco value={value}
language={monacoLanguage}
editor-height={this.editorHeight}
single-line={this.singleLineMode}
onValueChanged={e => this.onMonacoValueChanged(e.detail)}
ref={el => this.monacoEditor = el}/>
</div>
{fieldHint ? <p class="mt-2 text-sm text-gray-500">{fieldHint}</p> : undefined}
<input type="hidden" name={fieldName} value={value}/>
<input type="hidden" name={syntaxFieldName} value={selectedSyntax}/>
</div>
return (
<input type="text" id={fieldId} name={fieldName} value={value} class="focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"/>
);
}
}
Tunnel.injectProps(ElsaTextProperty, ['serverUrl', 'workflowDefinitionId']);

View file

@ -69,7 +69,7 @@ export class ElsaModalDialog {
data-transition-leave="ease-in duration-200"
data-transition-leave-start="opacity-100 translate-y-0 sm:scale-100"
data-transition-leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="hidden inline-block sm:align-top bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-top sm:max-w-4xl sm:w-full"
class="hidden inline-block sm:align-top bg-white rounded-lg text-left overflow-visible shadow-xl transform transition-all sm:my-8 sm:align-top sm:max-w-4xl sm:w-full"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="modal-content">
<slot name="content"/>

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,11 @@ export class MultilineDriver implements PropertyDisplayDriver {
const editorHeight = this.getEditorHeight(options);
const context: string = options.context;
return <elsa-text-property propertyDescriptor={property} propertyModel={prop} editor-height={editorHeight} context={context}/>;
return (
<elsa-property-editor propertyDescriptor={property} propertyModel={prop} editor-height={editorHeight} context={context}>
<elsa-text-property propertyDescriptor={property} propertyModel={prop}/>
</elsa-property-editor>
);
}
update(activity: ActivityModel, property: ActivityPropertyDescriptor, form: FormData) {

View file

@ -7,7 +7,11 @@ export class SingleLineDriver implements PropertyDisplayDriver {
display(activity: ActivityModel, property: ActivityPropertyDescriptor) {
const prop = getProperty(activity.properties, property.name);
return <elsa-text-property propertyDescriptor={property} propertyModel={prop} editor-height="2em" single-line={true}/>;
return (
<elsa-property-editor propertyDescriptor={property} propertyModel={prop} editor-height="2em" single-line={true}>
<elsa-text-property propertyDescriptor={property} propertyModel={prop}/>
</elsa-property-editor>
);
}
update(activity: ActivityModel, property: ActivityPropertyDescriptor, form: FormData) {

View file

@ -28,9 +28,8 @@ module.exports = {
borderColor: ['responsive', 'hover', 'focus']
},
plugins: [
require('@tailwindcss/ui')({
layout: 'sidebar',
}),
require('@tailwindcss/forms')
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
};