Incremental work on modal dialog service

This commit is contained in:
Sipke Schoorstra 2022-07-26 13:39:30 +02:00
parent e49f4642bc
commit 691565ed98
21 changed files with 522 additions and 521 deletions

View file

@ -29,8 +29,6 @@ import { WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs } fro
import { PublishClickedArgs as PublishClickedArgs1 } from "./modules/workflow-definitions/components/publish-button";
export namespace Components {
interface ElsaActivityDefinitionBrowser {
"hide": () => Promise<void>;
"show": () => Promise<void>;
}
interface ElsaActivityDefinitionEditor {
"activityDefinition"?: ActivityDefinition;
@ -170,15 +168,16 @@ export namespace Components {
"selectedLabels": Array<string>;
}
interface ElsaLabelsManager {
"hide": () => Promise<void>;
"show": () => Promise<void>;
}
interface ElsaModalDialog {
"actions": Array<ActionDefinition>;
"content": () => any;
"hide": (animate?: boolean) => Promise<void>;
"show": (animate?: boolean) => Promise<void>;
"size": string;
}
interface ElsaModalDialogContainer {
}
interface ElsaMonacoEditor {
"editorHeight": string;
"language": string;
@ -232,17 +231,13 @@ export namespace Components {
"tooltipContent": any;
"tooltipPosition"?: string;
}
interface ElsaVariableEditorDialog {
"hide": () => Promise<void>;
"show": () => Promise<void>;
interface ElsaVariableEditorDialogContent {
"variable": Variable;
}
interface ElsaVariablesEditor {
"variables"?: Array<Variable>;
}
interface ElsaWorkflowDefinitionBrowser {
"hide": () => Promise<void>;
"show": () => Promise<void>;
}
interface ElsaWorkflowDefinitionEditor {
"getCanvas": () => Promise<HTMLElsaCanvasElement>;
@ -269,8 +264,6 @@ export namespace Components {
"workflowDefinition"?: WorkflowDefinition;
}
interface ElsaWorkflowInstanceBrowser {
"hide": () => Promise<void>;
"show": () => Promise<void>;
}
interface ElsaWorkflowInstanceProperties {
"hide": () => Promise<void>;
@ -467,6 +460,12 @@ declare global {
prototype: HTMLElsaModalDialogElement;
new (): HTMLElsaModalDialogElement;
};
interface HTMLElsaModalDialogContainerElement extends Components.ElsaModalDialogContainer, HTMLStencilElement {
}
var HTMLElsaModalDialogContainerElement: {
prototype: HTMLElsaModalDialogContainerElement;
new (): HTMLElsaModalDialogContainerElement;
};
interface HTMLElsaMonacoEditorElement extends Components.ElsaMonacoEditor, HTMLStencilElement {
}
var HTMLElsaMonacoEditorElement: {
@ -545,11 +544,11 @@ declare global {
prototype: HTMLElsaTooltipElement;
new (): HTMLElsaTooltipElement;
};
interface HTMLElsaVariableEditorDialogElement extends Components.ElsaVariableEditorDialog, HTMLStencilElement {
interface HTMLElsaVariableEditorDialogContentElement extends Components.ElsaVariableEditorDialogContent, HTMLStencilElement {
}
var HTMLElsaVariableEditorDialogElement: {
prototype: HTMLElsaVariableEditorDialogElement;
new (): HTMLElsaVariableEditorDialogElement;
var HTMLElsaVariableEditorDialogContentElement: {
prototype: HTMLElsaVariableEditorDialogContentElement;
new (): HTMLElsaVariableEditorDialogContentElement;
};
interface HTMLElsaVariablesEditorElement extends Components.ElsaVariablesEditor, HTMLStencilElement {
}
@ -669,6 +668,7 @@ declare global {
"elsa-label-picker": HTMLElsaLabelPickerElement;
"elsa-labels-manager": HTMLElsaLabelsManagerElement;
"elsa-modal-dialog": HTMLElsaModalDialogElement;
"elsa-modal-dialog-container": HTMLElsaModalDialogContainerElement;
"elsa-monaco-editor": HTMLElsaMonacoEditorElement;
"elsa-multi-line-input": HTMLElsaMultiLineInputElement;
"elsa-multi-text-input": HTMLElsaMultiTextInputElement;
@ -682,7 +682,7 @@ declare global {
"elsa-studio": HTMLElsaStudioElement;
"elsa-switch-editor": HTMLElsaSwitchEditorElement;
"elsa-tooltip": HTMLElsaTooltipElement;
"elsa-variable-editor-dialog": HTMLElsaVariableEditorDialogElement;
"elsa-variable-editor-dialog-content": HTMLElsaVariableEditorDialogContentElement;
"elsa-variables-editor": HTMLElsaVariablesEditorElement;
"elsa-workflow-definition-browser": HTMLElsaWorkflowDefinitionBrowserElement;
"elsa-workflow-definition-editor": HTMLElsaWorkflowDefinitionEditorElement;
@ -838,11 +838,14 @@ declare namespace LocalJSX {
}
interface ElsaModalDialog {
"actions"?: Array<ActionDefinition>;
"content"?: () => any;
"onActionInvoked"?: (event: CustomEvent<ActionInvokedArgs>) => void;
"onHidden"?: (event: CustomEvent<any>) => void;
"onShown"?: (event: CustomEvent<any>) => void;
"size"?: string;
}
interface ElsaModalDialogContainer {
}
interface ElsaMonacoEditor {
"editorHeight"?: string;
"language"?: string;
@ -898,7 +901,7 @@ declare namespace LocalJSX {
"tooltipContent"?: any;
"tooltipPosition"?: string;
}
interface ElsaVariableEditorDialog {
interface ElsaVariableEditorDialogContent {
"onVariableChanged"?: (event: CustomEvent<Variable>) => void;
"variable"?: Variable;
}
@ -989,6 +992,7 @@ declare namespace LocalJSX {
"elsa-label-picker": ElsaLabelPicker;
"elsa-labels-manager": ElsaLabelsManager;
"elsa-modal-dialog": ElsaModalDialog;
"elsa-modal-dialog-container": ElsaModalDialogContainer;
"elsa-monaco-editor": ElsaMonacoEditor;
"elsa-multi-line-input": ElsaMultiLineInput;
"elsa-multi-text-input": ElsaMultiTextInput;
@ -1002,7 +1006,7 @@ declare namespace LocalJSX {
"elsa-studio": ElsaStudio;
"elsa-switch-editor": ElsaSwitchEditor;
"elsa-tooltip": ElsaTooltip;
"elsa-variable-editor-dialog": ElsaVariableEditorDialog;
"elsa-variable-editor-dialog-content": ElsaVariableEditorDialogContent;
"elsa-variables-editor": ElsaVariablesEditor;
"elsa-workflow-definition-browser": ElsaWorkflowDefinitionBrowser;
"elsa-workflow-definition-editor": ElsaWorkflowDefinitionEditor;
@ -1051,6 +1055,7 @@ declare module "@stencil/core" {
"elsa-label-picker": LocalJSX.ElsaLabelPicker & JSXBase.HTMLAttributes<HTMLElsaLabelPickerElement>;
"elsa-labels-manager": LocalJSX.ElsaLabelsManager & JSXBase.HTMLAttributes<HTMLElsaLabelsManagerElement>;
"elsa-modal-dialog": LocalJSX.ElsaModalDialog & JSXBase.HTMLAttributes<HTMLElsaModalDialogElement>;
"elsa-modal-dialog-container": LocalJSX.ElsaModalDialogContainer & JSXBase.HTMLAttributes<HTMLElsaModalDialogContainerElement>;
"elsa-monaco-editor": LocalJSX.ElsaMonacoEditor & JSXBase.HTMLAttributes<HTMLElsaMonacoEditorElement>;
"elsa-multi-line-input": LocalJSX.ElsaMultiLineInput & JSXBase.HTMLAttributes<HTMLElsaMultiLineInputElement>;
"elsa-multi-text-input": LocalJSX.ElsaMultiTextInput & JSXBase.HTMLAttributes<HTMLElsaMultiTextInputElement>;
@ -1064,7 +1069,7 @@ declare module "@stencil/core" {
"elsa-studio": LocalJSX.ElsaStudio & JSXBase.HTMLAttributes<HTMLElsaStudioElement>;
"elsa-switch-editor": LocalJSX.ElsaSwitchEditor & JSXBase.HTMLAttributes<HTMLElsaSwitchEditorElement>;
"elsa-tooltip": LocalJSX.ElsaTooltip & JSXBase.HTMLAttributes<HTMLElsaTooltipElement>;
"elsa-variable-editor-dialog": LocalJSX.ElsaVariableEditorDialog & JSXBase.HTMLAttributes<HTMLElsaVariableEditorDialogElement>;
"elsa-variable-editor-dialog-content": LocalJSX.ElsaVariableEditorDialogContent & JSXBase.HTMLAttributes<HTMLElsaVariableEditorDialogContentElement>;
"elsa-variables-editor": LocalJSX.ElsaVariablesEditor & JSXBase.HTMLAttributes<HTMLElsaVariablesEditorElement>;
"elsa-workflow-definition-browser": LocalJSX.ElsaWorkflowDefinitionBrowser & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionBrowserElement>;
"elsa-workflow-definition-editor": LocalJSX.ElsaWorkflowDefinitionEditor & JSXBase.HTMLAttributes<HTMLElsaWorkflowDefinitionEditorElement>;

View file

@ -0,0 +1,77 @@
import {Component, h, Prop, Event, EventEmitter, Method} from "@stencil/core";
import {DefaultActions, StorageDriverDescriptor, Variable} from "../../../models";
import {FormEntry} from "../../shared/forms/form-entry";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from '../../../data/descriptors-store';
@Component({
tag: 'elsa-variable-editor-dialog-content',
shadow: false
})
export class VariableEditorDialogContent {
@Prop() variable: Variable;
@Event() variableChanged: EventEmitter<Variable>;
render() {
const variable: Variable = this.variable ?? {name: '', type: 'Object'};
const variableType = variable.type;
const availableTypes: Array<string> = ['Object', 'String', 'Boolean', 'Int32', 'Int64', 'Single', 'Double']; // TODO: Fetch these from backend.
const storageDrivers: Array<StorageDriverDescriptor> = [{id: null, displayName: '-'}, ...descriptorsStore.storageDrivers];
return (
<div>
<form class="h-full flex flex-col bg-white" onSubmit={e => this.onSubmit(e)} method="post">
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Edit Variable</h2>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<FormEntry fieldId="variableName" label="Name" hint="The technical name of the variable.">
<input type="text" name="variableName" id="variableName" value={variable.name}/>
</FormEntry>
<FormEntry fieldId="variableType" label="Type" hint="The type of the variable.">
<select id="variableType" name="variableType">
{availableTypes.map(type => <option value={type} selected={type == variableType}>{type}</option>)}
</select>
</FormEntry>
<FormEntry fieldId="variableValue" label="Value" hint="The value of the variable.">
<input type="text" name="variableValue" id="variableValue" value={variable.value}/>
</FormEntry>
<FormEntry fieldId="variableStorageDriverId" label="Storage" hint="The storage to use when persisting the variable.">
<select id="variableStorageDriverId" name="variableStorageDriverId">
{storageDrivers.map(driver => {
const value = driver.id;
const text = driver.displayName;
const selected = value == variable.storageDriverId;
return <option value={value} selected={selected}>{text}</option>;
})}
</select>
</FormEntry>
</div>
</div>
</form>
</div>
);
}
private onSubmit = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const name = formData.get('variableName') as string;
const value = formData.get('variableValue') as string;
const type = formData.get('variableType') as string;
const driverId = formData.get('variableStorageDriverId') as string;
const variable = this.variable;
variable.name = name;
variable.type = type;
variable.value = value;
variable.storageDriverId = isNullOrWhitespace(driverId) ? null : driverId;
this.variableChanged.emit(variable);
};
}

View file

@ -1,96 +0,0 @@
import {Component, h, Prop, Event, EventEmitter, Method} from "@stencil/core";
import {DefaultActions, StorageDriverDescriptor, Variable} from "../../../models";
import {FormEntry} from "../../shared/forms/form-entry";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from '../../../data/descriptors-store';
@Component({
tag: 'elsa-variable-editor-dialog',
shadow: false
})
export class VariableEditorDialog {
private modalDialog: HTMLElsaModalDialogElement;
@Prop() variable: Variable;
@Event() variableChanged: EventEmitter<Variable>;
@Method()
public async show() {
await this.modalDialog.show();
}
@Method()
public async hide() {
await this.modalDialog.hide();
}
render() {
const variable: Variable = this.variable ?? {name: '', type: 'Object'};
const variableType = variable.type;
const cancelAction = DefaultActions.Cancel();
const saveAction = DefaultActions.Save();
const actions = [cancelAction, saveAction];
const availableTypes: Array<string> = ['Object', 'String', 'Boolean', 'Int32', 'Int64', 'Single', 'Double']; // TODO: Fetch these from backend.
const storageDrivers: Array<StorageDriverDescriptor> = [{ id: null, displayName: '-' }, ...descriptorsStore.storageDrivers];
return (
<div>
<form class="h-full flex flex-col bg-white" onSubmit={e => this.onSubmit(e)} method="post">
<elsa-modal-dialog ref={el => (this.modalDialog = el)} actions={actions}>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Edit Variable</h2>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<FormEntry fieldId="variableName" label="Name" hint="The technical name of the variable.">
<input type="text" name="variableName" id="variableName" value={variable.name}/>
</FormEntry>
<FormEntry fieldId="variableType" label="Type" hint="The type of the variable.">
<select id="variableType" name="variableType">
{availableTypes.map(type => <option value={type} selected={type == variableType}>{type}</option>)}
</select>
</FormEntry>
<FormEntry fieldId="variableValue" label="Value" hint="The value of the variable.">
<input type="text" name="variableValue" id="variableValue" value={variable.value}/>
</FormEntry>
<FormEntry fieldId="variableStorageDriverId" label="Storage" hint="The storage to use when persisting the variable.">
<select id="variableStorageDriverId" name="variableStorageDriverId">
{storageDrivers.map(driver => {
const value = driver.id;
const text = driver.displayName;
const selected = value == variable.storageDriverId;
return <option value={value} selected={selected}>{text}</option>;
})}
</select>
</FormEntry>
</div>
</div>
</elsa-modal-dialog>
</form>
</div>
);
}
private onSubmit = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const name = formData.get('variableName') as string;
const value = formData.get('variableValue') as string;
const type = formData.get('variableType') as string;
const driverId = formData.get('variableStorageDriverId') as string;
const variable = this.variable;
variable.name = name;
variable.type = type;
variable.value = value;
variable.storageDriverId = isNullOrWhitespace(driverId) ? null : driverId;
this.variableChanged.emit(variable);
await this.hide();
};
}

View file

@ -1,15 +1,22 @@
import {Component, h, Prop, State, Event, EventEmitter, Watch} from "@stencil/core";
import {DeleteIcon, EditIcon} from "../../icons/tooling";
import {StorageDriverDescriptor, Variable} from "../../../models";
import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from "../../../data/descriptors-store";
import {ModalDialogInstance, ModalDialogService} from "../../shared/modal-dialog";
import {Container} from "typedi";
@Component({
tag: 'elsa-variables-editor',
shadow: false
})
export class VariablesEditor {
private variableEditorDialog: HTMLElsaVariableEditorDialogElement;
private readonly modalDialogService: ModalDialogService;
private modalDialogInstance: ModalDialogInstance;
constructor() {
this.modalDialogService = Container.get(ModalDialogService);
}
@Prop() variables?: Array<Variable>;
@Event() variablesChanged: EventEmitter<Array<Variable>>;
@State() variablesState: Array<Variable> = [];
@ -68,7 +75,6 @@ export class VariablesEditor {
</tbody>
</table>
</div>
<elsa-variable-editor-dialog ref={el => this.variableEditorDialog = el} onVariableChanged={e => this.onVariableChanged(e)}/>
</div>
);
}
@ -94,14 +100,13 @@ export class VariablesEditor {
private onAddVariableClick = async () => {
const newVariableName = this.generateNewVariableName();
this.variableEditorDialog.variable = {name: newVariableName, type: 'Object', value: null};
await this.variableEditorDialog.show();
const variable = {name: newVariableName, type: 'Object', value: null};
this.modalDialogInstance = this.modalDialogService.show(() => <elsa-variable-editor-dialog-content variable={variable} onVariableChanged={this.onVariableChanged} />)
};
private onEditClick = async (e: Event, variable: Variable) => {
e.preventDefault();
this.variableEditorDialog.variable = variable;
await this.variableEditorDialog.show();
this.modalDialogInstance = this.modalDialogService.show(() => <elsa-variable-editor-dialog-content variable={variable} onVariableChanged={this.onVariableChanged}/>);
};
private onDeleteClick = (e: Event, variable: Variable) => {

View file

@ -0,0 +1,4 @@
export * from './modal-dialog';
export * from './modal-dialog-container';
export * from './models';
export * from './services';

View file

@ -0,0 +1,26 @@
import {Component, Host, h} from '@stencil/core';
import state from './state';
import {ModalDialogInstance} from "./models";
@Component({
tag: 'elsa-modal-dialog-container',
shadow: false,
})
export class ModalDialogContainer {
private onInstanceHidden = (instance: ModalDialogInstance) => {
let instances: Array<ModalDialogInstance> = state.instances;
instances = instances.filter(x => x != instance);
state.instances = instances;
}
render() {
const instances: Array<ModalDialogInstance> = state.instances;
return (
<Host>
{instances.map(instance => <elsa-modal-dialog content={instance.content} onHidden={() => this.onInstanceHidden(instance)}/>)}
</Host>
);
}
}

View file

@ -6,19 +6,17 @@ import {ActionDefinition, ActionInvokedArgs, ActionType} from '../../../models';
tag: 'elsa-modal-dialog',
shadow: false,
})
export class ElsaModalDialog {
@Prop() public actions: Array<ActionDefinition> = [];
@Prop() public size: string = 'sm:w-full sm:max-w-6xl';
@Event() public shown: EventEmitter;
@Event() public hidden: EventEmitter;
@Event() public actionInvoked: EventEmitter<ActionInvokedArgs>;
@State() private isVisible: boolean;
export class ModalDialog {
private overlay: HTMLElement
private modal: HTMLElement
render() {
return this.renderModal();
}
@Prop() actions: Array<ActionDefinition> = [];
@Prop() size: string = 'sm:w-full sm:max-w-6xl';
@Prop() content: () => any = () => <div/>;
@Event() shown: EventEmitter;
@Event() hidden: EventEmitter;
@Event() actionInvoked: EventEmitter<ActionInvokedArgs>;
@State() private isVisible: boolean = true;
@Method()
async show(animate: boolean = true) {
@ -65,7 +63,14 @@ export class ElsaModalDialog {
}
}
renderModal() {
componentDidRender() {
if(this.isVisible) {
enter(this.overlay);
enter(this.modal).then(this.shown.emit);
}
}
render() {
const actions = this.actions;
return (
@ -92,7 +97,7 @@ export class ElsaModalDialog {
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 ${this.size}`}
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="modal-content">
<slot/>
{this.content()}
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">

View file

@ -0,0 +1,6 @@
import {ActionDefinition} from "../../../models";
export interface ModalDialogInstance {
content: () => any;
actions: Array<ActionDefinition>;
}

View file

@ -0,0 +1,26 @@
import 'reflect-metadata';
import {Service} from "typedi";
import state from "./state";
import {ModalDialogInstance} from "./models";
@Service()
export class ModalDialogService {
show(content: () => any): ModalDialogInstance {
const newInstance: ModalDialogInstance = {
content: content,
actions: []
};
let instances: Array<ModalDialogInstance> = state.instances;
instances = [...instances, newInstance];
state.instances = instances;
return newInstance;
}
hide(instance: ModalDialogInstance) {
let instances: Array<ModalDialogInstance> = state.instances;
instances = instances.filter(x => x != instance);
state.instances = instances;
}
}

View file

@ -0,0 +1,7 @@
import { createStore } from '@stencil/store';
const { state, onChange } = createStore({
instances: []
});
export default state;

View file

@ -11,11 +11,8 @@ import toolbarButtonMenuItemStore from "../../../data/toolbar-button-menu-item-s
})
export class WorkflowToolbarMenu {
private readonly eventBus: EventBus;
//private readonly model: ToolbarMenu;
private menu: HTMLElement;
private element: HTMLElement;
private workflowDefinitionBrowser: HTMLElsaWorkflowDefinitionBrowserElement;
private workflowInstanceBrowser: HTMLElsaWorkflowInstanceBrowserElement;
constructor() {
this.eventBus = Container.get(EventBus);
@ -60,7 +57,6 @@ export class WorkflowToolbarMenu {
<a href="#" role="menuitem" tabindex="-1">Settings</a>
</div>
</div>
<elsa-workflow-instance-browser ref={el => this.workflowInstanceBrowser = el}/>
</Host>
);
}
@ -73,10 +69,6 @@ export class WorkflowToolbarMenu {
this.closeMenu();
}
private onWorkflowInstancesClick = async () => {
await this.workflowInstanceBrowser.show();
};
private onMenuItemClick = async (e: MouseEvent, menuItem: ToolbarMenuItem) => {
e.preventDefault();
await menuItem.onClick();

View file

@ -24,7 +24,7 @@ export class WorkflowToolbar {
<nav class="bg-gray-800">
<div class="mx-auto px-2 sm:px-6 lg:px-6">
<div class="relative flex items-center justify-end h-16">
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 z-20">
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0 z-30">
{/* Notifications*/}
<button
onClick={e => this.onNotificationClick(e)}

View file

@ -17,7 +17,7 @@
<elsa-studio server="https://localhost:5001/elsa/api" monaco-lib-path="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.31.1/min"></elsa-studio>
</div>
<elsa-custom-activities-manager></elsa-custom-activities-manager>
<elsa-labels-manager></elsa-labels-manager>
<!--<elsa-labels-manager></elsa-labels-manager>-->
<elsa-notifications-manager></elsa-notifications-manager>
<script type="module">
// Integration demos.

View file

@ -1,6 +1,6 @@
import { Component, Event, EventEmitter, h, Host, Method, State } from '@stencil/core';
import { Container } from 'typedi';
import { Filter, FilterProps } from './filter';
import {Component, Event, EventEmitter, h, Host, Method, State} from '@stencil/core';
import {Container} from 'typedi';
import {Filter, FilterProps} from './filter';
import {ActivityDefinitionsApi} from "../services/api";
import {ActivityDefinitionsOrderBy, ActivityDefinitionSummary} from "../models";
import {DefaultActions, PagedList, VersionOptions} from "../../../models";
@ -19,13 +19,12 @@ export class ActivityDefinitionBrowser {
static readonly START_PAGE = 0;
private activityDefinitionsApi: ActivityDefinitionsApi;
private modalDialog: HTMLElsaModalDialogElement;
private selectAllCheckbox: HTMLInputElement;
@Event() public activityDefinitionSelected: EventEmitter<ActivityDefinitionSummary>;
@Event() public newActivityDefinitionSelected: EventEmitter;
@State() private activityDefinitions: PagedList<ActivityDefinitionSummary> = { items: [], totalCount: 0 };
@State() private publishedActivityDefinitions: PagedList<ActivityDefinitionSummary> = { items: [], totalCount: 0 };
@State() private activityDefinitions: PagedList<ActivityDefinitionSummary> = {items: [], totalCount: 0};
@State() private publishedActivityDefinitions: PagedList<ActivityDefinitionSummary> = {items: [], totalCount: 0};
@State() private selectedActivityDefinitionIds: Array<string> = [];
@State() private currentPage: number = 0;
@State() private currentPageSize: number = ActivityDefinitionBrowser.DEFAULT_PAGE_SIZE;
@ -33,24 +32,14 @@ export class ActivityDefinitionBrowser {
@State() private labels?: string[];
@State() private selectAllChecked: boolean;
@Method()
public async show() {
await this.modalDialog.show();
await this.loadActivityDefinitions();
}
@Method()
public async hide() {
await this.modalDialog.hide();
}
public async componentWillLoad() {
this.activityDefinitionsApi = Container.get(ActivityDefinitionsApi);
await this.loadActivityDefinitions();
}
private onNewDefinitionClick = async () => {
this.newActivityDefinitionSelected.emit();
await this.hide();
};
private async onPublishClick(e: MouseEvent, ActivityDefinition: ActivityDefinitionSummary) {
@ -74,34 +63,33 @@ export class ActivityDefinitionBrowser {
}
private onDeleteManyClick = async () => {
await this.activityDefinitionsApi.deleteMany({ definitionIds: this.selectedActivityDefinitionIds });
await this.activityDefinitionsApi.deleteMany({definitionIds: this.selectedActivityDefinitionIds});
await this.loadActivityDefinitions();
};
private onPublishManyClick = async () => {
await this.activityDefinitionsApi.publishMany({ definitionIds: this.selectedActivityDefinitionIds });
await this.activityDefinitionsApi.publishMany({definitionIds: this.selectedActivityDefinitionIds});
await this.loadActivityDefinitions();
};
private onUnpublishManyClick = async () => {
await this.activityDefinitionsApi.unpublishMany({ definitionIds: this.selectedActivityDefinitionIds });
await this.activityDefinitionsApi.unpublishMany({definitionIds: this.selectedActivityDefinitionIds});
await this.loadActivityDefinitions();
};
private onActivityDefinitionClick = async (e: MouseEvent, ActivityDefinition: ActivityDefinitionSummary) => {
e.preventDefault();
this.activityDefinitionSelected.emit(ActivityDefinition);
await this.hide();
};
private async loadActivityDefinitions() {
const publishedVersionOptions: VersionOptions = { isPublished: true };
const publishedVersionOptions: VersionOptions = {isPublished: true};
const api = this.activityDefinitionsApi;
const latestActivityDefinitions = await api.list({
page: this.currentPage,
pageSize: this.currentPageSize,
versionOptions: { isLatest: true },
versionOptions: {isLatest: true},
orderBy: this.orderBy,
label: this.labels,
});
@ -143,91 +131,87 @@ export class ActivityDefinitionBrowser {
return (
<Host class="block">
<elsa-modal-dialog ref={el => (this.modalDialog = el)} actions={actions}>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Activity Definitions</h2>
<Filter {...filterProps} />
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Activity Definitions</h2>
<Filter {...filterProps} />
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<tr>
<th>
<input
type="checkbox"
value="true"
checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => (this.selectAllCheckbox = el)}
/>
</th>
<th>
<span class="lg:pl-2">Name</span>
</th>
<th class="optional align-right">Latest Version</th>
<th class="optional align-right">Published Version</th>
<th/>
</tr>
</thead>
<tbody>
{ActivityDefinitions.items.map(definition => {
const latestVersionNumber = definition.version;
const {isPublished} = definition;
const publishedVersion: ActivityDefinitionSummary = isPublished
? definition
: publishedActivityDefinitions.find(x => x.definitionId == definition.definitionId);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
const isSelected = this.selectedActivityDefinitionIds.findIndex(x => x === definition.definitionId) >= 0;
let displayName = definition.displayName || definition.typeName;
if (!displayName || displayName.trim().length == 0) displayName = 'Untitled';
return (
<tr>
<th>
<td>
<input
type="checkbox"
value="true"
checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => (this.selectAllCheckbox = el)}
value={definition.definitionId}
checked={isSelected}
onChange={e => this.onActivityDefinitionCheckChange(e, definition)}
/>
</th>
<th>
<span class="lg:pl-2">Name</span>
</th>
<th class="optional align-right">Latest Version</th>
<th class="optional align-right">Published Version</th>
<th />
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onActivityDefinitionClick(e, definition)} href="#" class="truncate hover:text-gray-600">
<span>{displayName}</span>
</a>
</div>
</td>
<td class="optional align-right">{latestVersionNumber}</td>
<td class="optional align-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu
menuItems={[
{text: 'Edit', clickHandler: e => this.onActivityDefinitionClick(e, definition), icon: <EditIcon/>},
isPublished
? {text: 'Unpublish', clickHandler: e => this.onUnPublishClick(e, definition), icon: <UnPublishIcon/>}
: {
text: 'Publish',
clickHandler: e => this.onPublishClick(e, definition),
icon: <PublishIcon/>,
},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, definition), icon: <DeleteIcon/>},
]}
/>
</td>
</tr>
</thead>
<tbody>
{ActivityDefinitions.items.map(definition => {
const latestVersionNumber = definition.version;
const { isPublished } = definition;
const publishedVersion: ActivityDefinitionSummary = isPublished
? definition
: publishedActivityDefinitions.find(x => x.definitionId == definition.definitionId);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
const isSelected = this.selectedActivityDefinitionIds.findIndex(x => x === definition.definitionId) >= 0;
let displayName = definition.displayName || definition.typeName;
if (!displayName || displayName.trim().length == 0) displayName = 'Untitled';
return (
<tr>
<td>
<input
type="checkbox"
value={definition.definitionId}
checked={isSelected}
onChange={e => this.onActivityDefinitionCheckChange(e, definition)}
/>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onActivityDefinitionClick(e, definition)} href="#" class="truncate hover:text-gray-600">
<span>{displayName}</span>
</a>
</div>
</td>
<td class="optional align-right">{latestVersionNumber}</td>
<td class="optional align-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu
menuItems={[
{ text: 'Edit', clickHandler: e => this.onActivityDefinitionClick(e, definition), icon: <EditIcon /> },
isPublished
? { text: 'Unpublish', clickHandler: e => this.onUnPublishClick(e, definition), icon: <UnPublishIcon /> }
: {
text: 'Publish',
clickHandler: e => this.onPublishClick(e, definition),
icon: <PublishIcon />,
},
{ text: 'Delete', clickHandler: e => this.onDeleteClick(e, definition), icon: <DeleteIcon /> },
]}
/>
</td>
</tr>
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated} />
</div>
{/*<confirm-dialog ref={el => this.confirmDialog = el} culture={this.culture}/>*/}
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated}/>
</div>
</elsa-modal-dialog>
</div>
</Host>
);
}
@ -259,7 +243,7 @@ export class ActivityDefinitionBrowser {
};
private getSelectAllState = () => {
const { items } = this.activityDefinitions;
const {items} = this.activityDefinitions;
const selectedWorkflowInstanceIds = this.selectedActivityDefinitionIds;
return items.findIndex(item => !selectedWorkflowInstanceIds.includes(item.definitionId)) < 0;
};

View file

@ -17,6 +17,7 @@ import {PublishClickedArgs} from "./components/publish-button";
import {ActivityDefinitionManager} from "./services/manager";
import {ActivityDefinition, ActivityDefinitionSummary, ActivityDefinitionUpdatedArgs} from "./models";
import {ActivityDefinitionsApi} from "./services/api";
import {ModalDialogInstance, ModalDialogService} from "../../components/shared/modal-dialog";
const FlowchartTypeName = 'Elsa.Flowchart';
@ -26,16 +27,16 @@ export class ActivityDefinitionsPlugin implements Plugin {
private readonly activityDefinitionManager: ActivityDefinitionManager;
private readonly activityDescriptorManager: ActivityDescriptorManager;
private readonly api: ActivityDefinitionsApi;
private readonly modalDialogService: ModalDialogService;
private activityDefinitionEditorElement: HTMLElsaActivityDefinitionEditorElement;
private activityDefinitionBrowserElement: HTMLElsaActivityDefinitionBrowserElement;
private activityDefinitionBrowserInstance: ModalDialogInstance;
constructor() {
this.eventBus = Container.get(EventBus);
this.activityDefinitionManager = Container.get(ActivityDefinitionManager);
this.api = Container.get(ActivityDefinitionsApi);
this.activityDescriptorManager = Container.get(ActivityDescriptorManager);
this.modalDialogService = Container.get(ModalDialogService);
const newActivityDefinitionItem: MenuItem = {
text: 'Activity Definition',
@ -50,10 +51,6 @@ export class ActivityDefinitionsPlugin implements Plugin {
newButtonItemStore.items = [...newButtonItemStore.items, newActivityDefinitionItem];
toolbarButtonMenuItemStore.items = [...toolbarButtonMenuItemStore.items, activityDefinitionBrowserItem];
studioComponentStore.modalComponents = [
...studioComponentStore.modalComponents,
() => <elsa-activity-definition-browser ref={el => this.activityDefinitionBrowserElement = el} onActivityDefinitionSelected={this.onActivityDefinitionSelected} onNewActivityDefinitionSelected={this.onNewActivityDefinitionClick}/>];
}
async initialize(): Promise<void> {
@ -105,11 +102,10 @@ export class ActivityDefinitionsPlugin implements Plugin {
ref={el => this.activityDefinitionEditorElement = el}/>;
};
private refreshActivityDescriptors = () => {
}
private onNewActivityDefinitionClick = async () => await this.newActivityDefinition();
private onNewActivityDefinitionClick = async () => {
await this.newActivityDefinition();
this.modalDialogService.hide(this.activityDefinitionBrowserInstance);
};
private onActivityDefinitionUpdated = async (e: CustomEvent<ActivityDefinitionUpdatedArgs>) => {
const activityDefinition = e.detail.activityDefinition;
@ -117,13 +113,15 @@ export class ActivityDefinitionsPlugin implements Plugin {
}
private onBrowseActivityDefinitions = async () => {
await this.activityDefinitionBrowserElement.show();
this.activityDefinitionBrowserInstance = this.modalDialogService.show(() =>
<elsa-activity-definition-browser onActivityDefinitionSelected={this.onActivityDefinitionSelected} onNewActivityDefinitionSelected={this.onNewActivityDefinitionClick}/>)
}
private onActivityDefinitionSelected = async (e: CustomEvent<ActivityDefinitionSummary>) => {
const definitionId = e.detail.definitionId;
const workflowDefinition = await this.api.get({definitionId});
this.showActivityDefinitionEditor(workflowDefinition);
this.modalDialogService.hide(this.activityDefinitionBrowserInstance);
}
private onPublishClicked = async (e: CustomEvent<PublishClickedArgs>) => {

View file

@ -23,7 +23,6 @@ export class LabelsManager {
private readonly workflowDefinitionLabelsApi: WorkflowDefinitionLabelsApi;
private readonly saveLabelsDebounced: () => void;
private workflowEditor: HTMLElsaWorkflowDefinitionEditorElement;
private modalDialog: HTMLElsaModalDialogElement;
private elsaClient: ElsaClient;
private definitionVersionId: string;
private assignedLabelIds: Array<string> = [];
@ -34,7 +33,7 @@ export class LabelsManager {
this.labelsApi = Container.get(LabelsApi);
this.workflowDefinitionLabelsApi = Container.get(WorkflowDefinitionLabelsApi);
this.eventBus.on(WorkflowEditorEventTypes.WorkflowEditor.Ready, this.onWorkflowEditorReady)
this.eventBus.on(ToolbarEventTypes.Displaying, this.onToolbarDisplaying);
//this.eventBus.on(ToolbarEventTypes.Displaying, this.onToolbarDisplaying);
this.eventBus.on(WorkflowPropertiesEditorEventTypes.Displaying, this.onWorkflowPropertiesEditorDisplaying);
this.eventBus.on(WorkflowEditorEventTypes.WorkflowDefinition.Imported, this.onWorkflowDefinitionImported);
this.saveLabelsDebounced = debounce(this.saveWorkflowLabels, 1000);
@ -42,16 +41,6 @@ export class LabelsManager {
@State() private createMode: boolean = false;
@Method()
async show() {
await this.modalDialog.show();
}
@Method()
async hide() {
await this.modalDialog.hide();
}
async componentWillLoad() {
this.elsaClient = await Container.get(ElsaApiClientProvider).getElsaClient();
labelStore.labels = await this.labelsApi.list();
@ -76,13 +65,13 @@ export class LabelsManager {
this.workflowEditor = e.workflowEditor;
}
private onToolbarDisplaying = (e: ToolbarDisplayingArgs) => {
e.menu.menuItems.push({
text: 'Labels',
onClick: this.onLabelsMenuItemClicked,
order: 3
});
}
// private onToolbarDisplaying = (e: ToolbarDisplayingArgs) => {
// e.menu.menuItems.push({
// text: 'Labels',
// onClick: this.onLabelsMenuItemClicked,
// order: 3
// });
// }
private onWorkflowPropertiesEditorDisplaying = (e: WorkflowPropertiesEditorDisplayingArgs) => {
const propertiesTabModel = e.model.tabModels.find(x => x.name == 'properties') as PropertiesTabModel;
@ -113,8 +102,6 @@ export class LabelsManager {
this.definitionVersionId = workflowDefinition.id;
}
private onLabelsMenuItemClicked = async () => await this.show();
private onSelectedLabelsChanged = async (e: CustomEvent<Array<string>>) => {
this.assignedLabelIds = e.detail;
await this.saveLabelsDebounced();
@ -129,41 +116,39 @@ export class LabelsManager {
return (
<Host class="block">
<elsa-modal-dialog ref={el => this.modalDialog = el} actions={actions}>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Labels</h2>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Labels</h2>
<div class="pl-4 pr-6">
<div class="flex justify-end">
<div class="flex-shrink">
<button type="button" class="btn" onClick={() => this.createMode = !this.createMode}>New label</button>
</div>
<div class="pl-4 pr-6">
<div class="flex justify-end">
<div class="flex-shrink">
<button type="button" class="btn" onClick={() => this.createMode = !this.createMode}>New label</button>
</div>
{createMode ? <elsa-label-creator onCreateLabelClicked={e => this.onCreateLabelClicked(e)}/> : undefined}
<div class="mt-5">
<div class="flex">
<div>
<p class="max-w-2xl text-sm text-gray-500">{labels.length == 1 ? '1 label' : `${labels.length} labels`}</p>
</div>
</div>
<div class="mt-5 border-t border-gray-200">
<div class="divide-y divide-gray-200">
{labels.map(label => <div class="border-top last:border-bottom border-solid border-gray-200">
<elsa-label-editor key={label.id}
label={label}
onLabelUpdated={e => this.onLabelUpdated(e)}
onLabelDeleted={e => this.onLabelDeleted(e)}
/>
</div>)}
</div>
</div>
</div>
</div>
{createMode ? <elsa-label-creator onCreateLabelClicked={e => this.onCreateLabelClicked(e)}/> : undefined}
<div class="mt-5">
<div class="flex">
<div>
<p class="max-w-2xl text-sm text-gray-500">{labels.length == 1 ? '1 label' : `${labels.length} labels`}</p>
</div>
</div>
<div class="mt-5 border-t border-gray-200">
<div class="divide-y divide-gray-200">
{labels.map(label => <div class="border-top last:border-bottom border-solid border-gray-200">
<elsa-label-editor key={label.id}
label={label}
onLabelUpdated={e => this.onLabelUpdated(e)}
onLabelDeleted={e => this.onLabelDeleted(e)}
/>
</div>)}
</div>
</div>
</div>
</div>
</elsa-modal-dialog>
</div>
</Host>
);
}

View file

@ -1,11 +1,11 @@
import { Component, Event, EventEmitter, h, Host, Method, Prop, State, Watch } from '@stencil/core';
import { DefaultActions, OrderBy, PagedList, VersionOptions} from '../../../models';
import { Container } from 'typedi';
import { ElsaApiClientProvider, ElsaClient } from '../../../services';
import { DeleteIcon, EditIcon, PublishIcon, UnPublishIcon } from '../../../components/icons/tooling';
import { Filter, FilterProps } from './filter';
import { PagerData } from '../../../components/shared/pager/pager';
import { updateSelectedWorkflowDefinitions } from '../services/utils';
import {Component, Event, EventEmitter, h, Host, Method, Prop, State, Watch} from '@stencil/core';
import {DefaultActions, OrderBy, PagedList, VersionOptions} from '../../../models';
import {Container} from 'typedi';
import {ElsaApiClientProvider, ElsaClient} from '../../../services';
import {DeleteIcon, EditIcon, PublishIcon, UnPublishIcon} from '../../../components/icons/tooling';
import {Filter, FilterProps} from './filter';
import {PagerData} from '../../../components/shared/pager/pager';
import {updateSelectedWorkflowDefinitions} from '../services/utils';
import {WorkflowDefinitionSummary} from "../models/entities";
import {WorkflowDefinitionsApi, WorkflowDefinitionsOrderBy} from "../services/api";
@ -20,7 +20,6 @@ export class WorkflowDefinitionBrowser {
static readonly START_PAGE = 0;
private readonly api: WorkflowDefinitionsApi;
private modalDialog: HTMLElsaModalDialogElement;
private selectAllCheckbox: HTMLInputElement;
constructor() {
@ -29,8 +28,8 @@ export class WorkflowDefinitionBrowser {
@Event() workflowDefinitionSelected: EventEmitter<WorkflowDefinitionSummary>;
@Event() public newWorkflowDefinitionSelected: EventEmitter;
@State() private workflowDefinitions: PagedList<WorkflowDefinitionSummary> = { items: [], totalCount: 0 };
@State() private publishedWorkflowDefinitions: PagedList<WorkflowDefinitionSummary> = { items: [], totalCount: 0 };
@State() private workflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], totalCount: 0};
@State() private publishedWorkflowDefinitions: PagedList<WorkflowDefinitionSummary> = {items: [], totalCount: 0};
@State() private selectedWorkflowDefinitionIds: Array<string> = [];
@State() private currentPage: number = 0;
@State() private currentPageSize: number = WorkflowDefinitionBrowser.DEFAULT_PAGE_SIZE;
@ -38,20 +37,12 @@ export class WorkflowDefinitionBrowser {
@State() private labels?: string[];
@State() private selectAllChecked: boolean;
@Method()
async show() {
await this.modalDialog.show();
async componentWillLoad() {
await this.loadWorkflowDefinitions();
}
@Method()
async hide() {
await this.modalDialog.hide();
}
private onNewDefinitionClick = async () => {
this.newWorkflowDefinitionSelected.emit();
await this.hide();
};
private async onPublishClick(e: MouseEvent, workflowDefinition: WorkflowDefinitionSummary) {
@ -75,29 +66,28 @@ export class WorkflowDefinitionBrowser {
}
private onDeleteManyClick = async () => {
await this.api.deleteMany({ definitionIds: this.selectedWorkflowDefinitionIds });
await this.api.deleteMany({definitionIds: this.selectedWorkflowDefinitionIds});
await this.loadWorkflowDefinitions();
};
private onPublishManyClick = async () => {
await this.api.publishMany({ definitionIds: this.selectedWorkflowDefinitionIds });
await this.api.publishMany({definitionIds: this.selectedWorkflowDefinitionIds});
await this.loadWorkflowDefinitions();
};
private onUnpublishManyClick = async () => {
await this.api.unpublishMany({ definitionIds: this.selectedWorkflowDefinitionIds });
await this.api.unpublishMany({definitionIds: this.selectedWorkflowDefinitionIds});
await this.loadWorkflowDefinitions();
};
private onWorkflowDefinitionClick = async (e: MouseEvent, workflowDefinition: WorkflowDefinitionSummary) => {
e.preventDefault();
this.workflowDefinitionSelected.emit(workflowDefinition);
await this.hide();
};
private async loadWorkflowDefinitions() {
const latestVersionOptions: VersionOptions = { isLatest: true };
const publishedVersionOptions: VersionOptions = { isPublished: true };
const latestVersionOptions: VersionOptions = {isLatest: true};
const publishedVersionOptions: VersionOptions = {isPublished: true};
// TODO: Load only json-based workflow definitions for now.
// Later, also allow CLR-based workflows to be "edited" (publish / unpublish / position activities / set variables, etc.)
@ -107,7 +97,7 @@ export class WorkflowDefinitionBrowser {
materializerName,
page: this.currentPage,
pageSize: this.currentPageSize,
versionOptions: { isLatest: true },
versionOptions: {isLatest: true},
orderBy: this.orderBy,
label: this.labels,
});
@ -149,7 +139,7 @@ export class WorkflowDefinitionBrowser {
};
private getSelectAllState = () => {
const { items } = this.workflowDefinitions;
const {items} = this.workflowDefinitions;
const selectedWorkflowInstanceIds = this.selectedWorkflowDefinitionIds;
return items.findIndex(item => !selectedWorkflowInstanceIds.includes(item.definitionId)) < 0;
};
@ -208,100 +198,96 @@ export class WorkflowDefinitionBrowser {
return (
<Host class="block">
<elsa-modal-dialog ref={el => (this.modalDialog = el)} actions={actions}>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Workflow Definitions</h2>
<Filter {...filterProps} />
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<tr>
<th>
<input
type="checkbox"
value="true"
checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => (this.selectAllCheckbox = el)}
/>
</th>
<th>
<span class="lg:pl-2">Name</span>
</th>
<th>Instances</th>
<th class="optional align-right">Latest Version</th>
<th class="optional align-right">Published Version</th>
<th />
</tr>
</thead>
<tbody>
{workflowDefinitions.items.map(workflowDefinition => {
const latestVersionNumber = workflowDefinition.version;
const { isPublished } = workflowDefinition;
const publishedVersion: WorkflowDefinitionSummary = isPublished
? workflowDefinition
: publishedWorkflowDefinitions.find(x => x.definitionId == workflowDefinition.definitionId);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Workflow Definitions</h2>
<Filter {...filterProps} />
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<tr>
<th>
<input
type="checkbox"
value="true"
checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => (this.selectAllCheckbox = el)}
/>
</th>
<th>
<span class="lg:pl-2">Name</span>
</th>
<th>Instances</th>
<th class="optional align-right">Latest Version</th>
<th class="optional align-right">Published Version</th>
<th/>
</tr>
</thead>
<tbody>
{workflowDefinitions.items.map(workflowDefinition => {
const latestVersionNumber = workflowDefinition.version;
const {isPublished} = workflowDefinition;
const publishedVersion: WorkflowDefinitionSummary = isPublished
? workflowDefinition
: publishedWorkflowDefinitions.find(x => x.definitionId == workflowDefinition.definitionId);
const publishedVersionNumber = !!publishedVersion ? publishedVersion.version : '-';
const isSelected = this.selectedWorkflowDefinitionIds.findIndex(x => x === workflowDefinition.definitionId) >= 0;
let workflowDisplayName = workflowDefinition.name;
const isSelected = this.selectedWorkflowDefinitionIds.findIndex(x => x === workflowDefinition.definitionId) >= 0;
let workflowDisplayName = workflowDefinition.name;
if (!workflowDisplayName || workflowDisplayName.trim().length == 0) workflowDisplayName = 'Untitled';
if (!workflowDisplayName || workflowDisplayName.trim().length == 0) workflowDisplayName = 'Untitled';
return (
<tr>
<td>
<input
type="checkbox"
value={workflowDefinition.definitionId}
checked={isSelected}
onChange={e => this.onWorkflowDefinitionCheckChange(e, workflowDefinition)}
/>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onWorkflowDefinitionClick(e, workflowDefinition)} href="#" class="truncate hover:text-gray-600">
<span>{workflowDisplayName}</span>
</a>
</div>
</td>
return (
<tr>
<td>
<input
type="checkbox"
value={workflowDefinition.definitionId}
checked={isSelected}
onChange={e => this.onWorkflowDefinitionCheckChange(e, workflowDefinition)}
/>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onWorkflowDefinitionClick(e, workflowDefinition)} href="#" class="truncate hover:text-gray-600">
<span>{workflowDisplayName}</span>
</a>
</div>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a href="#" class="truncate hover:text-gray-600">
Instances
</a>
</div>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a href="#" class="truncate hover:text-gray-600">
Instances
</a>
</div>
</td>
<td class="optional align-right">{latestVersionNumber}</td>
<td class="optional align-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu
menuItems={[
{ text: 'Edit', clickHandler: e => this.onWorkflowDefinitionClick(e, workflowDefinition), icon: <EditIcon /> },
isPublished
? { text: 'Unpublish', clickHandler: e => this.onUnPublishClick(e, workflowDefinition), icon: <UnPublishIcon /> }
: {
text: 'Publish',
clickHandler: e => this.onPublishClick(e, workflowDefinition),
icon: <PublishIcon />,
},
{ text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowDefinition), icon: <DeleteIcon /> },
]}
/>
</td>
</tr>
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated} />
</div>
{/*<confirm-dialog ref={el => this.confirmDialog = el} culture={this.culture}/>*/}
<td class="optional align-right">{latestVersionNumber}</td>
<td class="optional align-right">{publishedVersionNumber}</td>
<td class="pr-6">
<elsa-context-menu
menuItems={[
{text: 'Edit', clickHandler: e => this.onWorkflowDefinitionClick(e, workflowDefinition), icon: <EditIcon/>},
isPublished
? {text: 'Unpublish', clickHandler: e => this.onUnPublishClick(e, workflowDefinition), icon: <UnPublishIcon/>}
: {
text: 'Publish',
clickHandler: e => this.onPublishClick(e, workflowDefinition),
icon: <PublishIcon/>,
},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowDefinition), icon: <DeleteIcon/>},
]}
/>
</td>
</tr>
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated}/>
</div>
</elsa-modal-dialog>
</div>
</Host>
);
}

View file

@ -49,8 +49,7 @@ export class WorkflowDefinitionPropertiesEditor {
public render() {
const workflowDefinition = this.workflowDefinition;
const workflowDefinitionName = workflowDefinition?.name ?? 'Untitled';
const title = workflowDefinitionName;
const title = workflowDefinition?.name ?? 'Untitled';
const subTitle = 'Workflow Definition'
const tabs = this.model.tabModels.map(x => x.tab);

View file

@ -18,6 +18,7 @@ import {WorkflowDefinition, WorkflowDefinitionSummary} from "./models/entities";
import {WorkflowDefinitionUpdatedArgs} from "./models/ui";
import {PublishClickedArgs} from "./components/publish-button";
import {WorkflowDefinitionsApi} from "./services/api";
import {ModalDialogInstance, ModalDialogService} from "../../components/shared/modal-dialog";
const FlowchartTypeName = 'Elsa.Flowchart';
@ -25,14 +26,16 @@ const FlowchartTypeName = 'Elsa.Flowchart';
export class WorkflowDefinitionsPlugin implements Plugin {
private readonly eventBus: EventBus;
private readonly workflowDefinitionManager: WorkflowDefinitionManager;
private readonly modalDialogService: ModalDialogService;
private api: WorkflowDefinitionsApi;
private workflowDefinitionEditorElement: HTMLElsaWorkflowDefinitionEditorElement;
private workflowDefinitionBrowserElement: HTMLElsaWorkflowDefinitionBrowserElement;
private workflowDefinitionBrowserInstance: ModalDialogInstance;
constructor() {
this.eventBus = Container.get(EventBus);
this.api = Container.get(WorkflowDefinitionsApi);
this.workflowDefinitionManager = Container.get(WorkflowDefinitionManager);
this.modalDialogService = Container.get(ModalDialogService);
const newWorkflowDefinitionItem: MenuItem = {
text: 'Workflow Definition',
@ -47,10 +50,6 @@ export class WorkflowDefinitionsPlugin implements Plugin {
newButtonItemStore.items = [...newButtonItemStore.items, newWorkflowDefinitionItem];
toolbarButtonMenuItemStore.items = [...toolbarButtonMenuItemStore.items, workflowDefinitionBrowserItem];
studioComponentStore.modalComponents = [
...studioComponentStore.modalComponents,
() => <elsa-workflow-definition-browser ref={el => this.workflowDefinitionBrowserElement = el} onWorkflowDefinitionSelected={this.onWorkflowDefinitionSelected} onNewWorkflowDefinitionSelected={this.onNewWorkflowDefinitionClick}/>];
}
async initialize(): Promise<void> {
@ -100,7 +99,10 @@ export class WorkflowDefinitionsPlugin implements Plugin {
studioComponentStore.activeComponentFactory = () => <elsa-workflow-definition-editor workflowDefinition={workflowDefinition} onWorkflowUpdated={this.onWorkflowUpdated} ref={el => this.workflowDefinitionEditorElement = el}/>;
};
private onNewWorkflowDefinitionClick = async () => await this.newWorkflow();
private onNewWorkflowDefinitionClick = async () => {
await this.newWorkflow();
this.modalDialogService.hide(this.workflowDefinitionBrowserInstance);
};
private onWorkflowUpdated = async (e: CustomEvent<WorkflowDefinitionUpdatedArgs>) => {
const workflowDefinition = e.detail.workflowDefinition;
@ -108,13 +110,15 @@ export class WorkflowDefinitionsPlugin implements Plugin {
}
private onBrowseWorkflowDefinitions = async () => {
await this.workflowDefinitionBrowserElement.show();
this.workflowDefinitionBrowserInstance = this.modalDialogService.show(() =>
<elsa-workflow-definition-browser onWorkflowDefinitionSelected={this.onWorkflowDefinitionSelected} onNewWorkflowDefinitionSelected={this.onNewWorkflowDefinitionClick}/>)
}
private onWorkflowDefinitionSelected = async (e: CustomEvent<WorkflowDefinitionSummary>) => {
const definitionId = e.detail.definitionId;
const workflowDefinition = await this.api.get({definitionId});
this.showWorkflowDefinitionEditor(workflowDefinition);
this.modalDialogService.hide(this.workflowDefinitionBrowserInstance);
}
private onPublishClicked = async (e: CustomEvent<PublishClickedArgs>) => {

View file

@ -25,7 +25,6 @@ export class WorkflowInstanceBrowser {
private readonly workflowInstancesApi: WorkflowInstancesApi;
private readonly workflowDefinitionsApi: WorkflowDefinitionsApi;
private modalDialog: HTMLElsaModalDialogElement;
private selectAllCheckbox: HTMLInputElement;
private publishedOrLatestWorkflows: Array<WorkflowDefinitionSummary> = [];
@ -48,18 +47,11 @@ export class WorkflowInstanceBrowser {
@State() private selectedSubStatus?: WorkflowSubStatus;
@State() private orderBy?: OrderBy;
@Method()
public async show() {
await this.modalDialog.show();
async componentWillLoad() {
await this.loadWorkflows();
await this.loadWorkflowInstances();
}
@Method()
public async hide() {
await this.modalDialog.hide();
}
public render() {
const publishedOrLatestWorkflows = this.publishedOrLatestWorkflows;
const workflowInstances = this.workflowInstances;
@ -94,84 +86,80 @@ export class WorkflowInstanceBrowser {
return (
<Host class="block">
<elsa-modal-dialog ref={el => this.modalDialog = el} actions={actions} size="sm:w-fit max-w-fit">
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Workflow Instances</h2>
<div class="pt-4">
<h2 class="text-lg font-medium ml-4 mb-2">Workflow Instances</h2>
<Search onSearch={this.onSearch}/>
<Filter {...filterProps}/>
<Search onSearch={this.onSearch}/>
<Filter {...filterProps}/>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<tr>
<th>
<input type="checkbox" value="true" checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => this.selectAllCheckbox = el}/>
</th>
<th><span class="lg:pl-2">ID</span></th>
<th class="optional">Correlation</th>
<th>Workflow</th>
<th class="align-right">Version</th>
<th class="optional">Name</th>
<th>Status</th>
<th class="optional">Created</th>
<th class="optional">Finished</th>
<th class="optional">Executed</th>
<th class="optional">Faulted</th>
<th/>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{workflowInstances.items.map(workflowInstance => {
const statusColor = getSubStatusColor(workflowInstance.subStatus);
const isSelected = this.selectedWorkflowInstanceIds.findIndex(x => x === workflowInstance.id) >= 0;
const workflow: WorkflowDefinitionSummary = publishedOrLatestWorkflows.find(x => x.definitionId == workflowInstance.definitionId);
const workflowName = !!workflow ? (workflow.name || 'Untitled') : '(Definition not found)';
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="default-table">
<thead>
<tr>
<th>
<input type="checkbox" value="true" checked={this.getSelectAllState()}
onChange={e => this.onSelectAllCheckChange(e)}
ref={el => this.selectAllCheckbox = el}/>
</th>
<th><span class="lg:pl-2">ID</span></th>
<th class="optional">Correlation</th>
<th>Workflow</th>
<th class="align-right">Version</th>
<th class="optional">Name</th>
<th>Status</th>
<th class="optional">Created</th>
<th class="optional">Finished</th>
<th class="optional">Executed</th>
<th class="optional">Faulted</th>
<th/>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{workflowInstances.items.map(workflowInstance => {
const statusColor = getSubStatusColor(workflowInstance.subStatus);
const isSelected = this.selectedWorkflowInstanceIds.findIndex(x => x === workflowInstance.id) >= 0;
const workflow: WorkflowDefinitionSummary = publishedOrLatestWorkflows.find(x => x.definitionId == workflowInstance.definitionId);
const workflowName = !!workflow ? (workflow.name || 'Untitled') : '(Definition not found)';
return (
<tr>
<td>
<input type="checkbox" value={workflowInstance.id} checked={isSelected} onChange={e => this.onWorkflowInstanceCheckChange(e, workflowInstance)}/>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onWorkflowInstanceClick(e, workflowInstance)} href="#" class="truncate hover:text-gray-600"><span>{workflowInstance.id}</span></a>
</div>
</td>
return (
<tr>
<td>
<input type="checkbox" value={workflowInstance.id} checked={isSelected} onChange={e => this.onWorkflowInstanceCheckChange(e, workflowInstance)}/>
</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<a onClick={e => this.onWorkflowInstanceClick(e, workflowInstance)} href="#" class="truncate hover:text-gray-600"><span>{workflowInstance.id}</span></a>
</div>
</td>
<td class="optional">{workflowInstance.correlationId}</td>
<td class="optional">{workflowName}</td>
<td class="align-right">{workflowInstance.version}</td>
<td class="optional">{workflowInstance.name}</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<div class={`flex-shrink-0 w-2.5 h-2.5 rounded-full ${statusColor}`}/>
<span>{workflowInstance.status}</span>
</div>
</td>
<td class="optional">{formatTimestamp(workflowInstance.createdAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.finishedAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.lastExecutedAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.faultedAt, '-')}</td>
<td class="pr-6">
<elsa-context-menu menuItems={[
{text: 'Edit', clickHandler: e => this.onWorkflowInstanceClick(e, workflowInstance), icon: <EditIcon/>},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowInstance), icon: <DeleteIcon/>}
]}/>
</td>
</tr>
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated}/>
</div>
{/*<confirm-dialog ref={el => this.confirmDialog = el} culture={this.culture}/>*/}
<td class="optional">{workflowInstance.correlationId}</td>
<td class="optional">{workflowName}</td>
<td class="align-right">{workflowInstance.version}</td>
<td class="optional">{workflowInstance.name}</td>
<td>
<div class="flex items-center space-x-3 lg:pl-2">
<div class={`flex-shrink-0 w-2.5 h-2.5 rounded-full ${statusColor}`}/>
<span>{workflowInstance.status}</span>
</div>
</td>
<td class="optional">{formatTimestamp(workflowInstance.createdAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.finishedAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.lastExecutedAt, '-')}</td>
<td class="optional">{formatTimestamp(workflowInstance.faultedAt, '-')}</td>
<td class="pr-6">
<elsa-context-menu menuItems={[
{text: 'Edit', clickHandler: e => this.onWorkflowInstanceClick(e, workflowInstance), icon: <EditIcon/>},
{text: 'Delete', clickHandler: e => this.onDeleteClick(e, workflowInstance), icon: <DeleteIcon/>}
]}/>
</td>
</tr>
);
})}
</tbody>
</table>
<elsa-pager page={this.currentPage} pageSize={this.currentPageSize} totalCount={totalCount} onPaginated={this.onPaginated}/>
</div>
</elsa-modal-dialog>
</div>
</Host>
);
}
@ -270,7 +258,6 @@ export class WorkflowInstanceBrowser {
private onWorkflowInstanceClick = async (e: MouseEvent, workflowInstance: WorkflowInstanceSummary) => {
e.preventDefault();
this.workflowInstanceSelected.emit(workflowInstance);
await this.hide();
}
private onSelectAllCheckChange(e: Event) {

View file

@ -142,6 +142,7 @@ export class Studio {
return <Host>
{studioComponentStore.activeComponentFactory()}
{studioComponentStore.modalComponents.map(modal => modal())}
<elsa-modal-dialog-container/>
</Host>;
}
}