Incremental work on journal UI
This commit is contained in:
parent
45acc19b68
commit
47c4e5900f
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ export * from "./foreach";
|
|||
export * from "./delay";
|
||||
export * from "./timer";
|
||||
export * from "./run-javascript";
|
||||
export * from "./models";
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue