Incremental work on enhancements

This commit is contained in:
Sipke Schoorstra 2021-04-30 22:11:52 +02:00
parent ef4fa49571
commit d71d89cda0
9 changed files with 104 additions and 52 deletions

View file

@ -24,6 +24,7 @@ namespace Elsa.Design
/// <summary>
/// Optionally provide an object containing useful information for the list select items provider to determine what items to provide.
/// </summary>
[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
public object? Context { get; }
}
}

View file

@ -1,5 +1,5 @@
import { Component, h, Prop, Event, EventEmitter, State, Watch } from '@stencil/core';
import { MultiTextDefinition } from '../../../models/domain';
import { SelectListItem } from '../../../models';
@Component({
tag: 'elsa-input-tags-dropdown',
@ -11,15 +11,15 @@ export class ElsaInputTagsDropdown {
@Prop() fieldName?: string;
@Prop() fieldId?: string;
@Prop() placeHolder?: string = 'Add tag';
@Prop() values?: Array<string | MultiTextDefinition> = [];
@Prop() dropdownValues?: Array<MultiTextDefinition> = [];
@Event({ bubbles: true }) valueChanged: EventEmitter<Array<string | MultiTextDefinition>>;
@State() currentValues?: Array<MultiTextDefinition> = [];
@State() dropdownTags?: Array<MultiTextDefinition> = [];
@Prop() values?: Array<string | SelectListItem> = [];
@Prop() dropdownValues?: Array<SelectListItem> = [];
@Event({ bubbles: true }) valueChanged: EventEmitter<Array<string | SelectListItem>>;
@State() currentValues?: Array<SelectListItem> = [];
@State() dropdownTags?: Array<SelectListItem> = [];
@Watch('values')
valuesChangedHandler(newValue: Array<string | MultiTextDefinition>) {
let values: Array<MultiTextDefinition> = [];
valuesChangedHandler(newValue: Array<string | SelectListItem>) {
let values: Array<SelectListItem> = [];
newValue.forEach(value => {
this.dropdownValues.forEach(tag => {
@ -33,7 +33,7 @@ export class ElsaInputTagsDropdown {
componentWillLoad() {
this.dropdownTags = this.dropdownValues;
let values: Array<MultiTextDefinition> = [];
let values: Array<SelectListItem> = [];
this.values.forEach(value => {
this.dropdownValues.forEach(tag => {
@ -52,7 +52,7 @@ export class ElsaInputTagsDropdown {
e.preventDefault();
const input = e.target as HTMLSelectElement;
const currentTag: MultiTextDefinition = {
const currentTag: SelectListItem = {
text: input.options[input.selectedIndex].text.trim(),
value: input.value
}
@ -60,7 +60,7 @@ export class ElsaInputTagsDropdown {
if (currentTag.value.length == 0)
return;
const values: Array<MultiTextDefinition> = [...this.currentValues];
const values: Array<SelectListItem> = [...this.currentValues];
values.push(currentTag);
this.currentValues = values.distinct();
input.value = "Add";
@ -68,7 +68,7 @@ export class ElsaInputTagsDropdown {
await this.valueChanged.emit(values);
}
async onDeleteTagClick(e: any, currentTag: MultiTextDefinition) {
async onDeleteTagClick(e: any, currentTag: SelectListItem) {
e.preventDefault();
this.currentValues = this.currentValues.filter(tag => tag.value !== currentTag.value);
@ -77,7 +77,7 @@ export class ElsaInputTagsDropdown {
}
render() {
let values: Array<MultiTextDefinition> = this.currentValues || [];
let values: Array<SelectListItem> = this.currentValues || [];
if (!Array.isArray(values))
values = [];

View file

@ -1,6 +1,9 @@
import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames} from "../../../../models";
import {parseJson} from "../../../../utils/utils";
import {getSelectListItems} from "../../../../utils/select-list-items";
import Tunnel from "../../../../data/workflow-editor";
import {ElsaDropdownProperty} from "../elsa-dropdown-property/elsa-dropdown-property";
@Component({
tag: 'elsa-check-list-property',
@ -11,8 +14,11 @@ export class ElsaCheckListProperty {
@Prop() propertyDescriptor: ActivityPropertyDescriptor;
@Prop() propertyModel: ActivityDefinitionProperty;
@Prop({mutable: true}) serverUrl: string;
@State() currentValue?: string;
monacoEditor: HTMLElsaMonacoElement;
items: any[];
async componentWillLoad() {
this.currentValue = this.propertyModel.expressions[SyntaxNames.Json] || '[]';
@ -37,11 +43,15 @@ export class ElsaCheckListProperty {
this.currentValue = e.detail;
}
async componentWillRender(){
this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor);
}
render() {
const propertyDescriptor = this.propertyDescriptor;
const propertyModel = this.propertyModel;
const fieldId = propertyDescriptor.name;
const options = propertyDescriptor.options as Array<any>;
const items = this.items;
const values = parseJson(this.currentValue) || [];
return (
@ -51,11 +61,11 @@ export class ElsaCheckListProperty {
editor-height="2.75em"
single-line={true}>
<div class="max-w-lg space-y-3 my-4">
{options.map((option, index) => {
{items.map((item, index) => {
const inputId = `${fieldId}_${index}`;
const optionIsString = typeof(option) == 'string';
const value = optionIsString ? option : option.value;
const text = optionIsString ? option : option.text;
const optionIsString = typeof(item) == 'string';
const value = optionIsString ? item : item.value;
const text = optionIsString ? item : item.text;
const isSelected = values.findIndex(x => x == value) >= 0;
return (
@ -74,3 +84,5 @@ export class ElsaCheckListProperty {
);
}
}
Tunnel.injectProps(ElsaCheckListProperty, ['serverUrl']);

View file

@ -2,6 +2,7 @@ import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, RuntimeSelectListItemsProviderSettings, SelectListItem, SyntaxNames} from "../../../../models";
import Tunnel from "../../../../data/workflow-editor";
import {createElsaClient} from "../../../../services/elsa-client";
import {getSelectListItems} from "../../../../utils/select-list-items";
@Component({
tag: 'elsa-dropdown-property',
@ -33,22 +34,7 @@ export class ElsaDropdownProperty {
}
async componentWillRender(){
const propertyDescriptor = this.propertyDescriptor;
const options = propertyDescriptor.options;
let items = [];
if (!!options.runtimeSelectListItemsProviderType) {
items = await this.fetchRuntimeItems(options);
} else {
items = options as Array<any> || [];
}
this.items = items;
}
async fetchRuntimeItems(options: RuntimeSelectListItemsProviderSettings): Promise<Array<SelectListItem>>{
const elsaClient = createElsaClient(this.serverUrl);
return await elsaClient.designerApi.runtimeSelectItemsApi.get(options.runtimeSelectListItemsProviderType, options.context || {});
this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor);
}
render() {

View file

@ -1,6 +1,8 @@
import {Component, h, Prop, State} from '@stencil/core';
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames, MultiTextDefinition} from "../../../../models";
import {ActivityDefinitionProperty, ActivityPropertyDescriptor, SyntaxNames, SelectListItem} from "../../../../models";
import {parseJson} from "../../../../utils/utils";
import Tunnel from "../../../../data/workflow-editor";
import {getSelectListItems} from "../../../../utils/select-list-items";
@Component({
tag: 'elsa-multi-text-property',
@ -11,15 +13,20 @@ export class ElsaMultiTextProperty {
@Prop() propertyDescriptor: ActivityPropertyDescriptor;
@Prop() propertyModel: ActivityDefinitionProperty;
@Prop({mutable: true}) serverUrl: string;
@State() currentValue?: string;
items: any[];
async componentWillLoad() {
this.currentValue = this.propertyModel.expressions[SyntaxNames.Json] || '[]';
}
onValueChanged(newValue: Array<string | MultiTextDefinition>) {
onValueChanged(newValue: Array<string | number | boolean | SelectListItem>) {
const newValues = newValue.map(dropdown => {
if (typeof dropdown === 'string') return dropdown;
if (typeof dropdown === 'number') return dropdown.toString();
if (typeof dropdown === 'boolean') return dropdown.toString();
return dropdown.value;
})
@ -32,13 +39,17 @@ export class ElsaMultiTextProperty {
this.currentValue = e.detail;
}
createKeyValueOptions(options: Array<MultiTextDefinition>) {
createKeyValueOptions(options: Array<SelectListItem>) {
if (options === null)
return options;
return options.map(option => typeof option === 'string' ? { text: option, value: option } : option);
}
async componentWillRender(){
this.items = await getSelectListItems(this.serverUrl, this.propertyDescriptor);
}
render() {
const propertyDescriptor = this.propertyDescriptor;
const propertyModel = this.propertyModel;
@ -46,9 +57,9 @@ export class ElsaMultiTextProperty {
const fieldId = propertyName;
const fieldName = propertyName;
const values = parseJson(this.currentValue);
propertyDescriptor.options = propertyDescriptor.options || null;
const items = this.items;
const valueType = propertyDescriptor.options !== null ? 'dropdown' : 'multi-text';
const propertyOptions = this.createKeyValueOptions(propertyDescriptor.options);
const propertyOptions = this.createKeyValueOptions(items);
const elsaInputTags = valueType === 'multi-text' ?
<elsa-input-tags values={values} fieldId={fieldId} fieldName={fieldName} onValueChanged={e => this.onValueChanged(e.detail)} /> :
@ -65,3 +76,5 @@ export class ElsaMultiTextProperty {
)
}
}
Tunnel.injectProps(ElsaMultiTextProperty, ['serverUrl']);

View file

@ -168,11 +168,6 @@ export interface ConnectionDefinitionMapped {
outcome: string;
}
export interface MultiTextDefinition {
text: string;
value: string;
}
export interface ActivityDefinitionProperty {
name: string;
syntax?: string;

View file

@ -0,0 +1,20 @@
import {ActivityPropertyDescriptor, RuntimeSelectListItemsProviderSettings, SelectListItem} from "../models";
import {createElsaClient, ElsaClient} from "../services/elsa-client";
async function fetchRuntimeItems(serverUrl: string, options: RuntimeSelectListItemsProviderSettings): Promise<Array<SelectListItem>> {
const elsaClient = createElsaClient(serverUrl);
return await elsaClient.designerApi.runtimeSelectItemsApi.get(options.runtimeSelectListItemsProviderType, options.context || {});
}
export async function getSelectListItems(serverUrl: string, propertyDescriptor: ActivityPropertyDescriptor): Promise<Array<SelectListItem>> {
const options = propertyDescriptor.options;
let items = [];
if (!!options.runtimeSelectListItemsProviderType) {
items = await fetchRuntimeItems(serverUrl, options);
} else {
items = options as Array<any> || [];
}
return items || [];
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
@ -15,6 +16,13 @@ namespace Elsa.Samples.Server.Host.Activities
[Action]
public class VehicleActivity : Activity, IActivityPropertyOptionsProvider, IRuntimeSelectListItemsProvider
{
private Random _random;
public VehicleActivity()
{
_random = new Random();
}
[ActivityProperty(
UIHint = ActivityPropertyUIHints.Dropdown,
OptionsProvider = typeof(VehicleActivity),
@ -23,15 +31,18 @@ namespace Elsa.Samples.Server.Host.Activities
)]
public string? Brand { get; set; }
public object GetOptions(PropertyInfo property) => new RuntimeSelectListItemsProviderSettings(GetType());
public object GetOptions(PropertyInfo property) => new RuntimeSelectListItemsProviderSettings(GetType(), new VehicleContext(_random.Next(100)));
public ValueTask<IEnumerable<SelectListItem>> GetItemsAsync(object? context, CancellationToken cancellationToken = default)
{
var brands = new[] { "BMW", "Peugot", "Tesla" };
var vehicleContext = (VehicleContext) context!;
var brands = new[] { "BMW", "Peugot", "Tesla", vehicleContext.RandomNumber.ToString() };
var items = brands.Select(x => new SelectListItem(x)).ToList();
return new ValueTask<IEnumerable<SelectListItem>>(items);
}
protected override IActivityExecutionResult OnExecute() => Done(Brand);
}
public record VehicleContext(int RandomNumber);
}

View file

@ -3,9 +3,11 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Design;
using Elsa.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Annotations;
namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems
@ -17,10 +19,12 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems
public class Get : Controller
{
private readonly IServiceProvider _serviceProvider;
private readonly IContentSerializer _contentSerializer;
public Get(IServiceProvider serviceProvider)
public Get(IServiceProvider serviceProvider, IContentSerializer contentSerializer)
{
_serviceProvider = serviceProvider;
_contentSerializer = contentSerializer;
}
[HttpPost]
@ -31,13 +35,23 @@ namespace Elsa.Server.Api.Endpoints.Designer.RuntimeSelectListItems
OperationId = "Designer.RuntimeSelectListItems.Get",
Tags = new[] { "Designer.RuntimeSelectListItems" })
]
public async Task<IActionResult> Handle(string providerTypeName, object? context = default, CancellationToken cancellationToken = default)
public async Task<IActionResult> Handle([FromBody]RuntimeSelectListItemsContextHolder model, CancellationToken cancellationToken = default)
{
var type = Type.GetType(providerTypeName)!;
var provider = (IRuntimeSelectListItemsProvider)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, type);
var type = Type.GetType(model.ProviderTypeName)!;
var provider = (IRuntimeSelectListItemsProvider) ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, type);
var context = model?.Context;
var items = (await provider.GetItemsAsync(context, cancellationToken)).ToList();
var serializerSettings = _contentSerializer.GetSettings();
return Ok(items);
return Json(items, serializerSettings);
}
}
public record RuntimeSelectListItemsContextHolder
{
[FromRoute] public string ProviderTypeName { get; set; } = default!;
[JsonProperty(TypeNameHandling = TypeNameHandling.All)]
public object? Context { get; set; }
};
}