Incremental work on journal UI

This commit is contained in:
Sipke Schoorstra 2022-06-25 21:07:02 +02:00
parent 45acc19b68
commit 47c4e5900f
26 changed files with 244 additions and 107 deletions

View file

@ -119,7 +119,7 @@ export class DefaultActivityTemplate {
};
private renderPort = (activity: Activity, port: Port) => {
const canStartWorkflow = activity.canStartWorkflow;
const canStartWorkflow = activity?.canStartWorkflow == true;
const textColor = canStartWorkflow ? 'text-white' : 'text-gray-700';
const borderColor = port.name == this.selectedPortName ? 'border-blue-600' : 'border-gray-300';
const portName = camelCase(port.name);

View file

@ -33,7 +33,7 @@ export class VariablesEditor {
<button class="btn btn-primary" onClick={e => this.onAddVariableClick()}>Add variable</button>
</div>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table>
<table class="default-table">
<thead>
<tr>
<th scope="col">Name</th>

View file

@ -16,7 +16,7 @@ import {
ActivityUpdatedArgs,
DeleteActivityRequestedArgs
} from './activity-properties-editor';
import {PluginRegistry, ActivityNameFormatter, ActivityDriverRegistry, EventBus, findActivity, walkActivities, flattenList, flatten, ActivityNode} from '../../../services';
import {PluginRegistry, ActivityNameFormatter, ActivityDriverRegistry, EventBus, findActivity, walkActivities, flattenList, flatten, ActivityNode, createActivityMap} from '../../../services';
import {MonacoEditorSettings} from "../../../services/monaco-editor-settings";
import {Flowchart} from "../../activities/flowchart/models";
import {ActivityPropertyChangedEventArgs, WorkflowDefinitionPropsUpdatedArgs, WorkflowDefinitionUpdatedArgs, WorkflowEditorEventTypes} from "./models";
@ -164,13 +164,7 @@ export class WorkflowDefinitionEditor {
@Method()
async updateWorkflowDefinition(workflowDefinition: WorkflowDefinition): Promise<void> {
this.workflowDefinitionState = workflowDefinition;
const nodes = flatten(walkActivities(workflowDefinition.root));
const nodeMap = {};
for (const node of nodes)
nodeMap[node.activity.id] = node.activity;
this.nodeMap = nodeMap;
this.nodeMap = createActivityMap(flatten(walkActivities(workflowDefinition.root)));
if (this.currentWorkflowPath.length == 0)
this.currentWorkflowPath = [{activityId: workflowDefinition.root.id, portName: null}];

View file

@ -39,6 +39,8 @@ export interface WorkflowInstancePropertiesViewerModel {
export interface ActivityExecutionEventBlock {
activityId: string;
parents: Array<string>;
children: Array<string>;
completed: boolean;
timestamp: Date;
duration?: moment.Duration;

View file

@ -0,0 +1,9 @@
table.workflow-journal-table {
tbody tr td {
@apply pl-3 pr-0 py-2;
}
tbody tr td:first-child {
@apply pl-3 pr-0 py-2;
}
}

View file

@ -1,29 +1,36 @@
import {Component, h, Prop, State, Watch} from "@stencil/core";
import {ActionDefinition, ActionType, ActivityDescriptor, ActivityMetadata, WorkflowDefinition, WorkflowExecutionLogRecord, WorkflowInstance} from "../../../models";
import {ActionDefinition, ActionType, Activity, ActivityDescriptor, ActivityMetadata, WorkflowDefinition, WorkflowExecutionLogRecord, WorkflowInstance} from "../../../models";
import {Container} from "typedi";
import {ActivityNode, ElsaApiClientProvider, flatten, walkActivities} from "../../../services";
import {durationToString, formatTime, formatTimestamp, getDuration, isNullOrWhitespace} from "../../../utils";
import {ActivityIconRegistry, ActivityNode, createActivityMap, createActivityNodeMap, ElsaApiClientProvider, flatten, walkActivities} from "../../../services";
import {durationToString, formatTime, formatTimestamp, getDuration, Hash, isNullOrWhitespace} from "../../../utils";
import descriptorsStore from '../../../data/descriptors-store';
import {ActivityExecutionEventBlock} from "./models";
import {ActivityIconSize} from "../../icons/activities";
const PAGE_SIZE: number = 20;
@Component({
tag: 'elsa-workflow-journal',
shadow: false
shadow: false,
styleUrl: 'workflow-journal.scss'
})
export class WorkflowJournal {
private readonly elsaApiClientProvider: ElsaApiClientProvider;
private readonly iconRegistry: ActivityIconRegistry;
constructor() {
this.elsaApiClientProvider = Container.get(ElsaApiClientProvider);
this.iconRegistry = Container.get(ActivityIconRegistry);
}
@Prop() workflowInstance: WorkflowInstance;
@Prop() workflowDefinition: WorkflowDefinition;
@State() activityNodes: Array<ActivityNode> = [];
@State() nodeMap: Hash<ActivityNode> = {};
@State() nodes: Array<ActivityNode> = [];
@State() workflowExecutionLogRecords: Array<WorkflowExecutionLogRecord> = [];
@State() activityExecutionEventBlocks: Array<ActivityExecutionEventBlock> = [];
@State() rootBlocks: Array<ActivityExecutionEventBlock> = [];
@State() expandedBlocks: Array<ActivityExecutionEventBlock> = [];
@Watch('workflowInstance')
async onWorkflowInstanceChanged(value: string) {
@ -42,12 +49,14 @@ export class WorkflowJournal {
await this.loadJournalPage(0);
}
public render() {
render() {
const workflowInstance = this.workflowInstance;
const workflowDefinition = this.workflowDefinition;
const activityNodes = this.activityNodes;
const nodeMap = this.nodeMap;
const activityDescriptors: Array<ActivityDescriptor> = descriptorsStore.activityDescriptors;
const eventBlocks = this.activityExecutionEventBlocks;
const blocks = this.rootBlocks;
const iconRegistry = this.iconRegistry;
const expandedBlocks = this.expandedBlocks;
return (
@ -68,40 +77,19 @@ export class WorkflowJournal {
<div class="flex-1 relative">
<div class="absolute inset-0 overflow-y-scroll">
<table>
<table class="workflow-journal-table">
<thead>
<tr>
<th class="w-1"/>
<th>Time</th>
<th>Activity</th>
<th class="min-w-full">Activity</th>
<th>Status</th>
<th>Duration</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
{eventBlocks.map((eventBlock, index) => {
const activityNode = activityNodes.find(x => x.activity.id == eventBlock.activityId);
const activity = activityNode.activity;
const activityDescriptor = activityDescriptors.find(x => x.activityType == activityNode.activity.typeName);
const activityMetadata = activity.metadata;
const activityDisplayText = isNullOrWhitespace(activityMetadata.displayText) ? activity.typeName : activityMetadata.displayText;
const duration = durationToString(eventBlock.duration);
const status = eventBlock.completed ? 'Completed' : 'Started';
return (
<tr>
<td>{formatTime(eventBlock.timestamp)}</td>
<td>
<div>{activityDisplayText}</div>
<div class="font-bold">{activityDescriptor.activityType}</div>
</td>
<td>
<span class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800">{status}</span>
</td>
<td>{duration}</td>
</tr>
);
})}
{this.renderBlocks()}
</tbody>
</table>
</div>
@ -112,13 +100,72 @@ export class WorkflowJournal {
);
}
private renderBlocks = (activityId?: string) => {
const nodeMap = this.nodeMap;
const iconRegistry = this.iconRegistry;
const expandedBlocks = this.expandedBlocks;
const blocks = this.findChildBlocks(activityId);
return blocks.map((block, index) => {
const activityNode = nodeMap[block.activityId];
const activity = activityNode.activity;
const activityMetadata = activity.metadata;
const activityDisplayText = isNullOrWhitespace(activityMetadata.displayText) ? activity.id : activityMetadata.displayText;
const duration = durationToString(block.duration);
const status = block.completed ? 'Completed' : 'Started';
const icon = iconRegistry.get(activity.typeName)({size: ActivityIconSize.Small});
const expanded = !!expandedBlocks.find(x => x == block);
const toggleIcon = expanded
? (
<svg class="h-6 w-6 text-gray-500" 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"/>
<rect x="4" y="4" width="16" height="16" rx="2"/>
<line x1="9" y1="12" x2="15" y2="12"/>
</svg>
)
: (
<svg class="h-6 w-6 text-gray-500" 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"/>
<rect x="4" y="4" width="16" height="16" rx="2"/>
<line x1="9" y1="12" x2="15" y2="12"/>
<line x1="12" y1="9" x2="12" y2="15"/>
</svg>
);
return (
[<tr>
<td class="w-1">
{block.children.length > 0 ? (<a href="#" onClick={e => this.onBlockClick(e, block)}>
{toggleIcon}
</a>) : undefined}
</td>
<td>{formatTime(block.timestamp)}</td>
<td class="min-w-full">
<div class="flex items-center space-x-1">
<div class="flex-shrink">
<div class="bg-blue-500 rounded p-1">
{icon}
</div>
</div>
<div>{activityDisplayText}</div>
</div>
</td>
<td>
<span class="inline-flex rounded-full bg-green-100 px-2 text-xs font-semibold leading-5 text-green-800">{status}</span>
</td>
<td>{duration}</td>
</tr>, expanded ? this.renderBlocks(block.activityId) : undefined]
);
});
}
private createGraph = () => {
if (!this.workflowInstance || !this.workflowDefinition)
return;
const rootNode = walkActivities(this.workflowDefinition.root);
const nodes = flatten(rootNode);
this.activityNodes = nodes;
this.nodes = flatten(walkActivities(this.workflowDefinition.root));
this.nodeMap = createActivityNodeMap(this.nodes);
};
private loadJournalPage = async (page: number): Promise<void> => {
@ -131,18 +178,25 @@ export class WorkflowJournal {
const blocks = this.groupRecords(pageOfRecords.items);
this.workflowExecutionLogRecords = [...this.workflowExecutionLogRecords, ...pageOfRecords.items];
this.activityExecutionEventBlocks = [...this.activityExecutionEventBlocks, ...blocks];
this.rootBlocks = this.findChildBlocks(null);
}
private groupRecords = (records: Array<WorkflowExecutionLogRecord>): Array<ActivityExecutionEventBlock> => {
const startedEvents = records.filter(x => x.eventName == 'Started');
const completedEvents = records.filter(x => x.eventName == 'Completed');
const nodeMap = this.nodeMap;
const blocks: Array<ActivityExecutionEventBlock> = startedEvents.map(startedRecord => {
const completedRecord = completedEvents.find(x => x.activityInstanceId == startedRecord.activityInstanceId);
const duration = !!completedRecord ? getDuration(completedRecord.timestamp, startedRecord.timestamp) : null;
const node = nodeMap[startedRecord.activityId];
const parents = node.parents.map(x => x.activity.id);
const children = node.children.map(x => x.activity.id);
return {
activityId: startedRecord.activityId,
parents: parents,
children: children,
completed: !!completedRecord,
timestamp: startedRecord.timestamp,
duration: duration,
@ -153,4 +207,23 @@ export class WorkflowJournal {
return blocks;
};
private findChildBlocks = (parentActivityId?: string): Array<ActivityExecutionEventBlock> => {
const blocks = this.activityExecutionEventBlocks;
if (blocks.length == 0)
return [];
if (!!parentActivityId)
return blocks.filter(x => !!x.parents.find(p => p == parentActivityId));
return blocks.filter(x => x.parents.length == 0);
}
private onBlockClick = (e: MouseEvent, block: ActivityExecutionEventBlock) => {
e.preventDefault();
const existingBlock = this.expandedBlocks.find(x => x == block);
this.expandedBlocks = existingBlock ? this.expandedBlocks.filter(x => x != existingBlock) : [...this.expandedBlocks, block];
};
}

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {getActivityIconCssClass, ActivityIconSettings} from "./models";
export const DelayIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const DelayIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5"/>
<path d="M11 19.95a8 8 0 0 1 -5.3 -12.8" stroke-dasharray=".001 4.13"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const EventIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const EventIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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="13 3 13 10 19 10 11 21 11 14 5 14 13 3"/>
</svg>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {getActivityIconCssClass, ActivityIconSettings} from "./models";
export const FlowchartIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const FlowchartIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<rect x="3" y="3" width="6" height="6" rx="1"/>
<rect x="15" y="15" width="6" height="6" rx="1"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const ForEachIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const ForEachIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3"/>
<path d="M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3-3l3-3"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const HttpEndpointIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24"
export const HttpEndpointIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const IfIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
export const IfIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
);

View file

@ -8,3 +8,4 @@ export * from "./foreach";
export * from "./delay";
export * from "./timer";
export * from "./run-javascript";
export * from "./models";

View file

@ -0,0 +1,27 @@
export enum ActivityIconSize {
Small,
Medium,
Large
}
export interface ActivityIconSettings {
size?: ActivityIconSize;
}
export function getActivityIconCssClass(settings: ActivityIconSettings) {
const cssClasses = ['text-white', sizeToClass(settings.size)];
return cssClasses.join(' ');
}
function sizeToClass(size?: ActivityIconSize) {
switch (size) {
case ActivityIconSize.Small:
return 'h-4 w-4';
case ActivityIconSize.Medium:
return 'h-6 w-6';
case ActivityIconSize.Large:
return 'h-10 w-10';
default:
return 'h-6 w-6';
}
}

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const ReadLineIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const ReadLineIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M5 5h3m4 0h9"/>
<path d="M3 10h11m4 0h1"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const RunJavaScriptIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const RunJavaScriptIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const TimerIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const TimerIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<circle cx="12" cy="12" r="9"/>
<polyline points="12 7 12 12 15 15"/>

View file

@ -1,7 +1,8 @@
import {FunctionalComponent, h} from '@stencil/core';
import {ActivityIconSettings, getActivityIconCssClass} from "./models";
export const WriteLineIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
export const WriteLineIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4"/>
<line x1="13.5" y1="6.5" x2="17.5" y2="10.5"/>

View file

@ -154,7 +154,7 @@ export class WorkflowDefinitionBrowser {
<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>
<table class="default-table">
<thead>
<tr>
<th>

View file

@ -98,7 +98,7 @@ export class WorkflowInstanceBrowser {
<Filter {...filterProps}/>
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table>
<table class="default-table">
<thead>
<tr>
<th>

View file

@ -83,34 +83,34 @@ textarea {
/* Tables */
table {
@apply min-w-full;
}
thead tr {
@apply border-t border-gray-200;
}
thead tr {
@apply border-t border-gray-200;
}
thead tr th {
@apply px-6 py-3 border-b border-gray-200 bg-gray-50 text-xs leading-4 font-medium text-left text-gray-500 uppercase tracking-wider;
}
thead tr th {
@apply px-6 py-3 border-b border-gray-200 bg-gray-50 text-xs leading-4 font-medium text-left text-gray-500 uppercase tracking-wider;
}
thead tr th.optional, tbody tr td.optional {
@apply hidden md:table-cell;
}
thead tr th.optional, tbody tr td.optional {
@apply hidden md:table-cell;
}
table thead tr th.align-right, tbody tr td.align-right {
@apply text-right;
}
table thead tr th.align-right, tbody tr td.align-right {
@apply text-right;
}
tbody {
@apply bg-white divide-y divide-gray-100;
}
tbody {
@apply bg-white divide-y divide-gray-100;
}
tbody tr td {
@apply px-6 py-3 text-sm leading-5 text-gray-500 font-medium;
}
tbody tr td {
@apply px-6 py-3 text-sm leading-5 text-gray-500 font-medium;
}
tbody tr td:first-child {
@apply px-6 py-3 whitespace-nowrap text-sm leading-5 font-medium text-gray-900;
tbody tr td:first-child {
@apply px-6 py-3 whitespace-nowrap text-sm leading-5 font-medium text-gray-900;
}
}
/* Pager */

View file

@ -3,6 +3,7 @@ import {FunctionalComponent, h} from '@stencil/core';
import {Container, Service} from "typedi";
import {ActivityIconRegistry} from "../../services";
import {Plugin} from "../../models";
import {ActivityIconSettings, getActivityIconCssClass} from "../../components/icons/activities";
@Service()
export class SequencePlugin implements Plugin {
@ -11,12 +12,12 @@ export class SequencePlugin implements Plugin {
constructor() {
const iconRegistry = Container.get(ActivityIconRegistry);
iconRegistry.add(SequencePlugin.ActivityTypeName, () => <SequenceIcon/>);
iconRegistry.add(SequencePlugin.ActivityTypeName, settings => <SequenceIcon size={settings?.size}/>);
}
}
const SequenceIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
const SequenceIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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"/>
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5"/>
<path d="M11 19.95a8 8 0 0 1 -5.3 -12.8" stroke-dasharray=".001 4.13"/>

View file

@ -4,6 +4,7 @@ import {Container, Service} from "typedi";
import {ActivityIconRegistry, InputControlRegistry, PortProviderRegistry} from "../../services";
import {Plugin} from "../../models";
import {SwitchPortProvider} from "./switch-port-provider";
import {ActivityIconSettings, getActivityIconCssClass} from "../../components/icons/activities";
@Service()
export class SwitchPlugin implements Plugin {
@ -17,12 +18,12 @@ export class SwitchPlugin implements Plugin {
inputControlRegistry.add('switch-editor', c => <elsa-switch-editor inputContext={c}/>);
portProviderRegistry.add(activityTypeName, () => Container.get(SwitchPortProvider));
iconRegistry.add(SwitchPlugin.ActivityTypeName, () => <SwitchIcon/>);
iconRegistry.add(SwitchPlugin.ActivityTypeName, settings => <SwitchIcon size={settings?.size}/>);
}
}
const SwitchIcon: FunctionalComponent = () => (
<svg class="h-6 w-6 text-white" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
const SwitchIcon: FunctionalComponent<ActivityIconSettings> = (settings) => (
<svg class={getActivityIconCssClass(settings)} 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="15 4 19 4 19 8"/>
<line x1="14.75" y1="9.25" x2="19" y2="4"/>

View file

@ -16,7 +16,7 @@ import {
} from "../components/icons/activities";
export type ActivityType = string;
export type ActivityIcon = () => any;
export type ActivityIcon = (ActivityIconSettings?) => any;
// A registry of activity icons.
@Service()
@ -24,16 +24,16 @@ export class ActivityIconRegistry {
private iconMap: Map<ActivityType, ActivityIcon> = new Map<ActivityType, ActivityIcon>();
constructor() {
this.add('Elsa.WriteLine', () => <WriteLineIcon/>);
this.add('Elsa.ReadLine', () => <ReadLineIcon/>);
this.add('Elsa.If', () => <IfIcon/>);
this.add('Elsa.Flowchart', () => <FlowchartIcon/>);
this.add('Elsa.HttpEndpoint', () => <HttpEndpointIcon/>);
this.add('Elsa.ForEach', () => <ForEachIcon/>);
this.add('Elsa.Delay', () => <DelayIcon/>);
this.add('Elsa.Timer', () => <TimerIcon/>);
this.add('Elsa.Event', () => <EventIcon/>);
this.add('Elsa.RunJavaScript', () => <RunJavaScriptIcon/>);
this.add('Elsa.WriteLine', settings => <WriteLineIcon size={settings?.size}/>);
this.add('Elsa.ReadLine', settings => <ReadLineIcon size={settings?.size}/>);
this.add('Elsa.If', settings => <IfIcon size={settings?.size}/>);
this.add('Elsa.Flowchart', settings => <FlowchartIcon size={settings?.size}/>);
this.add('Elsa.HttpEndpoint', settings => <HttpEndpointIcon size={settings?.size}/>);
this.add('Elsa.ForEach', settings => <ForEachIcon size={settings?.size}/>);
this.add('Elsa.Delay', settings => <DelayIcon size={settings?.size}/>);
this.add('Elsa.Timer', settings => <TimerIcon size={settings?.size}/>);
this.add('Elsa.Event', settings => <EventIcon size={settings?.size}/>);
this.add('Elsa.RunJavaScript', settings => <RunJavaScriptIcon size={settings?.size}/>);
}
public add(activityType: ActivityType, icon: ActivityIcon) {

View file

@ -4,6 +4,7 @@ import {Container} from "typedi"
import {Activity, ActivityDescriptor} from "../models";
import {PortProviderRegistry} from "./port-provider-registry";
import descriptorsStore from '../data/descriptors-store';
import {Hash} from "../utils";
export interface ActivityNode {
activity: Activity;
@ -17,6 +18,24 @@ export interface ActivityPort {
port: string;
}
export function createActivityMap(nodes: Array<ActivityNode>): Hash<Activity> {
const map = {};
for (const node of nodes)
map[node.activity.id] = node.activity;
return map;
}
export function createActivityNodeMap(nodes: Array<ActivityNode>): Hash<ActivityNode> {
const map = {};
for (const node of nodes)
map[node.activity.id] = node;
return map;
}
export function walkActivities(root: Activity): ActivityNode {
const descriptors = descriptorsStore.activityDescriptors;
const collectedActivities = new Set<Activity>([root]);

View file

@ -10,7 +10,7 @@ const dev: boolean = process.argv && process.argv.indexOf('--dev') > -1;
export const config: Config = {
namespace: 'elsa-workflows-designer',
globalStyle: 'src/global/tailwind.css',
globalStyle: 'src/global/tailwind.scss',
outputTargets: [
{
type: 'dist',