stable designer on dagre-d3-version
This commit is contained in:
parent
aabf9ff2c2
commit
511bf7ce7d
11594
src/designer/elsa-workflows-studio/package-lock.json
generated
11594
src/designer/elsa-workflows-studio/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,7 @@
|
|||
"push:preview": "npm publish --access-public --tag preview",
|
||||
"build:prod": "stencil build --dev",
|
||||
"build:dev": "stencil build --dev --tailwind:dev",
|
||||
"start:dev": "stencil build --dev --tailwind:dev --watch --serve",
|
||||
"start:dev": "node --max_old_space_size=8192 --optimize_for_size --stack_size=8192 ./node_modules/.bin/stencil build --dev --tailwind:dev --watch --serve",
|
||||
"start": "stencil build --watch --serve",
|
||||
"test": "stencil test --spec --e2e",
|
||||
"test.watch": "stencil test --spec --e2e --watchAll",
|
||||
|
|
@ -39,6 +39,8 @@
|
|||
"autoprefixer": "10.2.5",
|
||||
"axios": "^0.21.1",
|
||||
"cssnano": "^4.1.10",
|
||||
"d3": "^6.6.2",
|
||||
"dagre-d3": "^0.6.4",
|
||||
"el-transition": "^0.0.7",
|
||||
"eslint": "7.23.0",
|
||||
"js-event-bus": "^1.0.3",
|
||||
|
|
@ -55,6 +57,8 @@
|
|||
"typescript-tslint-plugin": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3": "^6.3.0",
|
||||
"@types/dagre-d3": "^0.6.2",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export namespace Components {
|
|||
"propertyModel": ActivityDefinitionProperty;
|
||||
}
|
||||
interface ElsaDesignerTree {
|
||||
"destroyJsPlumb": () => Promise<void>;
|
||||
"model": WorkflowModel;
|
||||
}
|
||||
interface ElsaDesignerTreeActivity {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
background-image: url(build/assets/tile.png);
|
||||
background-repeat: repeat;
|
||||
background-size: 30px 30px;
|
||||
background-color: #FBFBFB;
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
|
||||
div.canvas {
|
||||
|
|
@ -77,7 +77,7 @@ div.tree {
|
|||
width: 0;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
.activity {
|
||||
min-width: 316px;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -85,3 +85,24 @@ div.tree {
|
|||
white-space: normal;
|
||||
display: inline-block;
|
||||
}
|
||||
*/
|
||||
|
||||
.node.activity {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.node.activity .label-container {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.node.add .label-container {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
.edgePath {
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
.outcome {
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,148 +1,125 @@
|
|||
import {Component, Host, h, Prop, State, Event, EventEmitter, Listen, Watch, Method} from '@stencil/core';
|
||||
import {Map, addConnection, findActivity, getChildActivities, getInboundConnections, getOutboundConnections, removeActivity} from '../../../../utils/utils';
|
||||
import {cleanup, destroy, updateConnections} from '../../../../utils/jsplumb-helper';
|
||||
import {ActivityDescriptor, ActivityDesignDisplayContext, ActivityModel, ConnectionModel, EventTypes, WorkflowModel, WorkflowPersistenceBehavior} from "../../../../models";
|
||||
import {eventBus} from '../../../../services/event-bus';
|
||||
import jsPlumb from "jsplumb";
|
||||
import { Component, Host, h, Prop, State, Event, EventEmitter, Listen, Watch, Method } from '@stencil/core';
|
||||
import { Map, addConnection, findActivity, getChildActivities, getInboundConnections, getOutboundConnections, removeActivity } from '../../../../utils/utils';
|
||||
import { cleanup, destroy, updateConnections } from '../../../../utils/jsplumb-helper';
|
||||
import { ActivityDescriptor, ActivityDesignDisplayContext, ActivityModel, ActivityTraits, ConnectionModel, EventTypes, WorkflowModel, WorkflowPersistenceBehavior } from '../../../../models';
|
||||
import { eventBus } from '../../../../services/event-bus';
|
||||
import jsPlumb from 'jsplumb';
|
||||
import * as d3 from 'd3';
|
||||
import dagreD3 from 'dagre-d3';
|
||||
import { toggle } from 'el-transition';
|
||||
import { registerClickOutside } from 'stencil-click-outside';
|
||||
import state from "../../../../utils/store";
|
||||
import uuid = jsPlumb.jsPlumbUtil.uuid;
|
||||
import {ActivityIcon} from "../../../icons/activity-icon";
|
||||
import { ActivityIcon } from '../../../icons/activity-icon';
|
||||
|
||||
@Component({
|
||||
tag: 'elsa-designer-tree',
|
||||
styleUrls: ['elsa-designer-tree.css'],
|
||||
assetsDirs: ['assets'],
|
||||
shadow: false
|
||||
shadow: false,
|
||||
})
|
||||
export class ElsaWorkflowDesigner {
|
||||
|
||||
@Prop() model: WorkflowModel = { activities: [], connections: [], persistenceBehavior: WorkflowPersistenceBehavior.WorkflowBurst };
|
||||
@Event({eventName: 'workflow-changed', bubbles: true, composed: true, cancelable: true}) workflowChanged: EventEmitter<WorkflowModel>
|
||||
@State() workflowModel: WorkflowModel
|
||||
el: HTMLElement
|
||||
canvasElement: HTMLElement;
|
||||
@Event({ eventName: 'workflow-changed', bubbles: true, composed: true, cancelable: true }) workflowChanged: EventEmitter<WorkflowModel>;
|
||||
@State() workflowModel: WorkflowModel;
|
||||
|
||||
el: HTMLElement;
|
||||
svg: SVGElement;
|
||||
inner: SVGElement;
|
||||
svgD3Selected: d3.Selection<SVGElement, unknown, null, undefined>;
|
||||
innerD3Selected: d3.Selection<SVGElement, unknown, null, undefined>;
|
||||
zoomParams: { x: number; y: number; scale: number } = { x: 0, y: 0, scale: 1 };
|
||||
dagreD3Renderer: dagreD3.Render = new dagreD3.render();
|
||||
|
||||
graph: dagreD3.graphlib.Graph = new dagreD3.graphlib.Graph().setGraph({});
|
||||
zoom: d3.ZoomBehavior<Element, unknown>;
|
||||
parentActivityId?: string;
|
||||
parentActivityOutcome?: string;
|
||||
activityDisplayContexts: Map<ActivityDesignDisplayContext> = {};
|
||||
|
||||
@State() contextMenu: {
|
||||
shown: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
activity?: ActivityModel | null;
|
||||
} = {
|
||||
shown: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
activity: null,
|
||||
};
|
||||
|
||||
handleContextMenuChange(x: number, y: number, shown: boolean, activity: ActivityModel) {
|
||||
this.contextMenu = {
|
||||
shown,
|
||||
x,
|
||||
y,
|
||||
activity,
|
||||
};
|
||||
}
|
||||
|
||||
@Watch('model')
|
||||
handleModelChanged(newValue: WorkflowModel) {
|
||||
this.workflowModel = newValue;
|
||||
}
|
||||
|
||||
@Method()
|
||||
async destroyJsPlumb(){
|
||||
destroy();
|
||||
// @Listen('edit-activity')
|
||||
handleEditActivity(activity: ActivityModel) {
|
||||
this.showActivityEditor(activity, true);
|
||||
}
|
||||
|
||||
@Listen('edit-activity')
|
||||
handleEditActivity(e: CustomEvent<ActivityModel>) {
|
||||
this.showActivityEditor(e.detail, true);
|
||||
updateWorkflowModel(model: WorkflowModel) {
|
||||
this.workflowModel = model;
|
||||
this.workflowChanged.emit(model);
|
||||
|
||||
console.log('rerendering')
|
||||
this.rerenderTree();
|
||||
}
|
||||
|
||||
@Listen('remove-activity')
|
||||
handleRemoveActivity(e: CustomEvent<ActivityModel>) {
|
||||
this.removeActivity(e.detail.activityId);
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.workflowModel = this.model;
|
||||
|
||||
eventBus.on(EventTypes.ActivityPicked, async args => {
|
||||
const activityDescriptor = (args as ActivityDescriptor);
|
||||
const connectFromRoot = !this.parentActivityOutcome || this.parentActivityOutcome == "";
|
||||
const sourceId = connectFromRoot ? null : this.parentActivityId;
|
||||
const targetId = connectFromRoot ? this.parentActivityId : null;
|
||||
const activityModel = this.addActivity(activityDescriptor, sourceId, targetId, this.parentActivityOutcome);
|
||||
|
||||
this.showActivityEditor(activityModel, false);
|
||||
});
|
||||
|
||||
eventBus.on(EventTypes.UpdateActivity, async args => {
|
||||
const activityModel = (args as ActivityModel);
|
||||
this.updateActivity(activityModel);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillRender() {
|
||||
destroy();
|
||||
|
||||
const activityModels = this.workflowModel.activities;
|
||||
const displayContexts: Map<ActivityDesignDisplayContext> = {};
|
||||
|
||||
for (const model of activityModels) {
|
||||
|
||||
const description = model.description;
|
||||
const bodyText = description && description.length > 0 ? description : undefined;
|
||||
const bodyDisplay = bodyText ? <p>{bodyText}</p> : undefined;
|
||||
|
||||
const displayContext: ActivityDesignDisplayContext = {
|
||||
activityModel: model,
|
||||
activityIcon: <ActivityIcon/>,
|
||||
bodyDisplay: bodyDisplay,
|
||||
outcomes: [...model.outcomes]
|
||||
};
|
||||
|
||||
eventBus.emit(EventTypes.ActivityDesignDisplaying, this, displayContext);
|
||||
displayContexts[model.activityId] = displayContext;
|
||||
}
|
||||
|
||||
this.activityDisplayContexts = displayContexts;
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
const canvasElement = this.canvasElement;
|
||||
const connections = this.getJsPlumbConnections();
|
||||
const sourceEndpoints = this.getJsPlumbSourceEndpoints();
|
||||
const targets = this.getJsPlumbTargets();
|
||||
|
||||
const invalidConnections = updateConnections(
|
||||
canvasElement,
|
||||
connections,
|
||||
sourceEndpoints,
|
||||
targets,
|
||||
connection => this.onConnectionCreated(connection),
|
||||
connection => this.onConnectionDetached(connection));
|
||||
|
||||
this.removeInvalidConnections(invalidConnections);
|
||||
connectedCallback() {
|
||||
eventBus.on(EventTypes.ActivityPicked, this.onActivityPicked);
|
||||
eventBus.on(EventTypes.UpdateActivity, this.onUpdateActivity);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
eventBus.off(EventTypes.ActivityPicked, this.onActivityPicked);
|
||||
eventBus.off(EventTypes.UpdateActivity, this.onUpdateActivity);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
showActivityPicker() {
|
||||
eventBus.emit(EventTypes.ShowActivityPicker);
|
||||
}
|
||||
onActivityPicked = async args => {
|
||||
const activityDescriptor = args as ActivityDescriptor;
|
||||
const connectFromRoot = !this.parentActivityOutcome || this.parentActivityOutcome == '';
|
||||
const sourceId = connectFromRoot ? null : this.parentActivityId;
|
||||
const targetId = connectFromRoot ? this.parentActivityId : null;
|
||||
const activityModel = this.addActivity(activityDescriptor, sourceId, targetId, this.parentActivityOutcome);
|
||||
this.showActivityEditor(activityModel, false);
|
||||
};
|
||||
|
||||
showActivityEditor(activity: ActivityModel, animate: boolean) {
|
||||
eventBus.emit(EventTypes.ShowActivityEditor, this, activity, animate);
|
||||
}
|
||||
|
||||
getRootActivities(): Array<ActivityModel> {
|
||||
return getChildActivities(this.workflowModel, null);
|
||||
}
|
||||
onUpdateActivity = args => {
|
||||
const activityModel = args as ActivityModel;
|
||||
this.updateActivity(activityModel);
|
||||
};
|
||||
|
||||
addActivity(activityDescriptor: ActivityDescriptor, sourceActivityId?: string, targetActivityId?: string, outcome?: string): ActivityModel {
|
||||
|
||||
outcome = outcome || 'Done';
|
||||
|
||||
const activity: ActivityModel =
|
||||
{
|
||||
activityId: uuid(),
|
||||
type: activityDescriptor.type,
|
||||
outcomes: activityDescriptor.outcomes,
|
||||
displayName: activityDescriptor.displayName,
|
||||
properties: []
|
||||
};
|
||||
const activity: ActivityModel = {
|
||||
activityId: uuid(),
|
||||
type: activityDescriptor.type,
|
||||
outcomes: activityDescriptor.outcomes,
|
||||
displayName: activityDescriptor.displayName,
|
||||
properties: [],
|
||||
};
|
||||
|
||||
for (const property of activityDescriptor.properties) {
|
||||
activity.properties[property.name] = {
|
||||
syntax: '',
|
||||
expression: ''
|
||||
}
|
||||
expression: '',
|
||||
};
|
||||
}
|
||||
|
||||
const workflowModel = {...this.workflowModel, activities: [...this.workflowModel.activities, activity]};
|
||||
const workflowModel = { ...this.workflowModel, activities: [...this.workflowModel.activities, activity] };
|
||||
|
||||
if (targetActivityId) {
|
||||
const existingConnection = workflowModel.connections.find(x => x.targetId == targetActivityId && x.outcome == outcome);
|
||||
|
|
@ -152,12 +129,12 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
const replacementConnection = {
|
||||
...existingConnection,
|
||||
sourceId: activity.activityId
|
||||
sourceId: activity.activityId,
|
||||
};
|
||||
|
||||
workflowModel.connections.push(replacementConnection);
|
||||
} else {
|
||||
workflowModel.connections.push({sourceId: activity.activityId, targetId: targetActivityId, outcome: outcome});
|
||||
workflowModel.connections.push({ sourceId: activity.activityId, targetId: targetActivityId, outcome: outcome });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,14 +146,14 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
const replacementConnection = {
|
||||
...existingConnection,
|
||||
targetId: activity.activityId
|
||||
}
|
||||
targetId: activity.activityId,
|
||||
};
|
||||
|
||||
workflowModel.connections.push(replacementConnection);
|
||||
const connection: ConnectionModel = {sourceId: activity.activityId, targetId: existingConnection.targetId, outcome};
|
||||
const connection: ConnectionModel = { sourceId: activity.activityId, targetId: existingConnection.targetId, outcome };
|
||||
workflowModel.connections.push(connection);
|
||||
} else {
|
||||
const connection: ConnectionModel = {sourceId: sourceActivityId, targetId: activity.activityId, outcome: outcome};
|
||||
const connection: ConnectionModel = { sourceId: sourceActivityId, targetId: activity.activityId, outcome: outcome };
|
||||
workflowModel.connections.push(connection);
|
||||
}
|
||||
}
|
||||
|
|
@ -185,301 +162,315 @@ export class ElsaWorkflowDesigner {
|
|||
return activity;
|
||||
}
|
||||
|
||||
removeActivity(activityId: string) {
|
||||
let workflowModel = {...this.workflowModel};
|
||||
const incomingConnections = getInboundConnections(workflowModel, activityId);
|
||||
const outgoingConnections = getOutboundConnections(workflowModel, activityId);
|
||||
|
||||
addConnection(sourceActivityId: string, targetActivityId: string, outcome: string) {
|
||||
const workflowModel = { ...this.workflowModel };
|
||||
const connection: ConnectionModel = { sourceId: sourceActivityId, targetId: targetActivityId, outcome: outcome };
|
||||
workflowModel.connections.push(connection);
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
}
|
||||
|
||||
updateActivity(activity: ActivityModel) {
|
||||
let workflowModel = { ...this.workflowModel };
|
||||
const activities = [...workflowModel.activities];
|
||||
const index = activities.findIndex(x => x.activityId === activity.activityId);
|
||||
activities[index] = activity;
|
||||
this.updateWorkflowModel({ ...workflowModel, activities: activities });
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.workflowModel = this.model;
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
this.svgD3Selected = d3.select(this.svg);
|
||||
this.innerD3Selected = d3.select(this.inner);
|
||||
this.applyZoom();
|
||||
this.setEnities();
|
||||
this.renderNodes();
|
||||
}
|
||||
|
||||
|
||||
componentWillRender() {
|
||||
const activityModels = this.workflowModel.activities;
|
||||
const displayContexts: Map<ActivityDesignDisplayContext> = {};
|
||||
const activityDescriptors: Array<ActivityDescriptor> = state.activityDescriptors;
|
||||
|
||||
for (const model of activityModels) {
|
||||
|
||||
const descriptor = activityDescriptors.find(x => x.type == model.type);
|
||||
const description = model.description;
|
||||
const bodyText = description && description.length > 0 ? description : undefined;
|
||||
const bodyDisplay = bodyText ? <p>{bodyText}</p> : undefined;
|
||||
const color = (descriptor.traits &= ActivityTraits.Trigger) == ActivityTraits.Trigger ? 'rose' : 'light-blue';
|
||||
|
||||
const displayContext: ActivityDesignDisplayContext = {
|
||||
activityModel: model,
|
||||
activityIcon: <ActivityIcon color={color}/>,
|
||||
bodyDisplay: bodyDisplay,
|
||||
outcomes: [...model.outcomes]
|
||||
};
|
||||
|
||||
eventBus.emit(EventTypes.ActivityDesignDisplaying, this, displayContext);
|
||||
displayContexts[model.activityId] = displayContext;
|
||||
}
|
||||
|
||||
this.activityDisplayContexts = displayContexts;
|
||||
}
|
||||
|
||||
showActivityPicker() {
|
||||
eventBus.emit(EventTypes.ShowActivityPicker);
|
||||
}
|
||||
|
||||
showActivityEditor(activity: ActivityModel, animate: boolean) {
|
||||
eventBus.emit(EventTypes.ShowActivityEditor, this, activity, animate);
|
||||
}
|
||||
|
||||
|
||||
removeActivity(activity: ActivityModel) {
|
||||
let workflowModel = { ...this.workflowModel };
|
||||
const incomingConnections = getInboundConnections(workflowModel, activity.activityId);
|
||||
const outgoingConnections = getOutboundConnections(workflowModel, activity.activityId);
|
||||
|
||||
// Remove activity (will also remove its connections).
|
||||
workflowModel = removeActivity(workflowModel, activityId);
|
||||
workflowModel = removeActivity(workflowModel, activity.activityId);
|
||||
|
||||
|
||||
// For each incoming activity, try to connect it to a outgoing activity based on outcome.
|
||||
for (const incomingConnection of incomingConnections) {
|
||||
const incomingActivity = findActivity(workflowModel, incomingConnection.sourceId);
|
||||
const outgoingConnection = outgoingConnections.find(x => x.outcome === incomingConnection.outcome);
|
||||
|
||||
if (outgoingConnection)
|
||||
workflowModel = addConnection(workflowModel, incomingActivity.activityId, outgoingConnection.targetId, incomingConnection.outcome);
|
||||
if (outgoingConnection) workflowModel = addConnection(workflowModel, incomingActivity.activityId, outgoingConnection.targetId, incomingConnection.outcome);
|
||||
}
|
||||
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
}
|
||||
|
||||
updateActivity(activity: ActivityModel) {
|
||||
let workflowModel = {...this.workflowModel};
|
||||
const activities = [...workflowModel.activities];
|
||||
const index = activities.findIndex(x => x.activityId === activity.activityId);
|
||||
activities[index] = activity;
|
||||
this.updateWorkflowModel({...workflowModel, activities: activities});
|
||||
// onOutcomeButtonClick(e: Event) {
|
||||
// console.log(e)
|
||||
// e.preventDefault();
|
||||
// console.log(e)
|
||||
// // this.parentActivityId = activityId;
|
||||
// // this.parentActivityOutcome = outcome;
|
||||
// this.showActivityPicker();
|
||||
// }
|
||||
|
||||
applyZoom() {
|
||||
this.zoom = d3.zoom().on('zoom', event => {
|
||||
const { transform } = event;
|
||||
this.innerD3Selected.attr('transform', transform);
|
||||
this.zoomParams = {
|
||||
x: transform.x,
|
||||
y: transform.y,
|
||||
scale: transform.k,
|
||||
};
|
||||
});
|
||||
this.svgD3Selected.call(this.zoom);
|
||||
}
|
||||
|
||||
updateWorkflowModel(model: WorkflowModel) {
|
||||
this.workflowModel = model;
|
||||
this.workflowChanged.emit(model);
|
||||
}
|
||||
setEnities() {
|
||||
this.graph = new dagreD3.graphlib.Graph().setGraph({})
|
||||
console.log(this.workflowModel.activities)
|
||||
|
||||
removeInvalidConnections(invalidConnections: Array<ConnectionModel>){
|
||||
if (invalidConnections.length > 0) {
|
||||
this.workflowModel.activities.forEach(activity => {
|
||||
this.graph.setNode(activity.activityId, this.createActivityOptions(activity));
|
||||
|
||||
const isValid = (connection: ConnectionModel): boolean => invalidConnections.findIndex(x => x.targetId == connection.targetId && x.sourceId == connection.sourceId && x.outcome == connection.outcome) < 0;
|
||||
const workflowModel = {...this.workflowModel};
|
||||
|
||||
workflowModel.connections = workflowModel.connections.filter(isValid);
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
}
|
||||
}
|
||||
|
||||
getJsPlumbConnections(): Array<any> {
|
||||
const rootActivities = getChildActivities(this.workflowModel, null);
|
||||
|
||||
const rootConnections = rootActivities.flatMap(x => {
|
||||
return [
|
||||
{
|
||||
sourceId: `start-button`,
|
||||
sourceActivityId: undefined,
|
||||
targetId: `start-button-plus-${x.activityId}`,
|
||||
targetActivityId: x.activityId,
|
||||
outcome: undefined
|
||||
},
|
||||
{
|
||||
sourceId: `start-button-plus-${x.activityId}`,
|
||||
sourceActivityId: undefined,
|
||||
targetId: `activity-${x.activityId}`,
|
||||
targetActivityId: x.activityId,
|
||||
outcome: undefined
|
||||
}]
|
||||
activity.outcomes.forEach(outcome => {
|
||||
this.graph.setNode(`${activity.activityId}/${outcome}`, { shape: 'rect', outcome, activity, label: this.renderOutcomeButton(), labelType: 'html', class: 'add' });
|
||||
this.graph.setEdge(activity.activityId, `${activity.activityId}/${outcome}`, {
|
||||
label: `<p class="outcome">${outcome}</p>`,
|
||||
labelpos: 'c',
|
||||
labelType: 'html',
|
||||
arrowhead: 'undirected',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const sourceConnections = this.workflowModel.activities.flatMap(activity => {
|
||||
const displayContext = this.activityDisplayContexts[activity.activityId];
|
||||
return displayContext.outcomes.map(x =>
|
||||
({
|
||||
sourceId: `activity-${activity.activityId}`,
|
||||
sourceActivityId: activity.activityId,
|
||||
targetId: `${activity.activityId}-${x}`,
|
||||
targetActivityId: undefined,
|
||||
outcome: x
|
||||
}));
|
||||
this.workflowModel.connections.forEach(({ sourceId, targetId, outcome }) => {
|
||||
this.graph.setEdge(`${sourceId}/${outcome}`, targetId, { arrowhead: 'undirected' });
|
||||
});
|
||||
|
||||
const connections = this.workflowModel.connections.flatMap(x => [
|
||||
{
|
||||
sourceId: `${x.sourceId}-${x.outcome}`,
|
||||
sourceActivityId: x.sourceId,
|
||||
targetId: `activity-${x.targetId}`,
|
||||
targetActivityId: x.targetId,
|
||||
outcome: x.outcome
|
||||
}]
|
||||
);
|
||||
|
||||
return [...rootConnections, ...connections, ...sourceConnections];
|
||||
console.log(this.workflowModel.activities)
|
||||
console.log(this.graph.nodes().map(node => this.graph.node(node)))
|
||||
}
|
||||
|
||||
getJsPlumbSourceEndpoints(): Array<any> {
|
||||
const rootActivities = getChildActivities(this.workflowModel, null);
|
||||
|
||||
const rootSourceEndpoints = rootActivities.map(x =>
|
||||
({
|
||||
sourceId: `start-button-plus-${x.activityId}`,
|
||||
sourceActivityId: x.activityId,
|
||||
outcome: undefined
|
||||
}));
|
||||
|
||||
const otherSourceEndpoints = this.workflowModel.activities.flatMap(activity => {
|
||||
const displayContext = this.activityDisplayContexts[activity.activityId];
|
||||
// closeContextMenu() {
|
||||
// leave(this.contextMenu);
|
||||
// }
|
||||
|
||||
return displayContext.outcomes.map(x =>
|
||||
({
|
||||
sourceId: `${activity.activityId}-${x}`,
|
||||
sourceActivityId: activity.activityId,
|
||||
outcome: x
|
||||
}));
|
||||
});
|
||||
|
||||
return [...rootSourceEndpoints, ...otherSourceEndpoints];
|
||||
onDeleteActivityClick(e: Event) {
|
||||
e.preventDefault();
|
||||
this.removeActivity(this.contextMenu.activity);
|
||||
this.handleContextMenuChange(0, 0, false, null);
|
||||
}
|
||||
|
||||
getJsPlumbTargets() {
|
||||
return this.workflowModel.activities.map(x =>
|
||||
({
|
||||
targetId: `activity-${x.activityId}`,
|
||||
targetActivityId: x.activityId
|
||||
onEditActivityClick(e: Event) {
|
||||
e.preventDefault();
|
||||
this.handleEditActivity(this.contextMenu.activity);
|
||||
this.handleContextMenuChange(0, 0, false, null);
|
||||
}
|
||||
|
||||
renderNodes() {
|
||||
const prevTransform = this.innerD3Selected.attr('transform');
|
||||
const scaleAfter = this.zoomParams.scale;
|
||||
this.svgD3Selected.call(this.zoom.scaleTo, 1);
|
||||
this.dagreD3Renderer(this.innerD3Selected as any, this.graph as any);
|
||||
this.svgD3Selected.call(this.zoom.scaleTo, scaleAfter);
|
||||
this.innerD3Selected.attr('transform', prevTransform);
|
||||
d3.selectAll('.node.add').each((n: any) => {
|
||||
const node = this.graph.node(n) as any;
|
||||
d3.select(node.elem).on('click', e => {
|
||||
e.preventDefault();
|
||||
this.parentActivityId = node.activity.activityId;
|
||||
this.parentActivityOutcome = node.outcome;
|
||||
if(e.shiftKey){
|
||||
d3.select(node.elem).select('svg').classed("text-green-400", true).classed("text-gray-400", false).classed('hover:text-blue-500', false)
|
||||
return;
|
||||
}
|
||||
));
|
||||
this.showActivityPicker();
|
||||
});
|
||||
});
|
||||
d3.selectAll('.node.activity').each((n: any) => {
|
||||
const node = this.graph.node(n) as any;
|
||||
// console.log(d3.select(node.elem).select('.context-menu'));
|
||||
d3.select(node.elem).on('click', () => {
|
||||
this.addConnection(this.parentActivityId, node.activity.activityId, this.parentActivityOutcome)
|
||||
this.parentActivityId = null;
|
||||
this.parentActivityOutcome = null;
|
||||
})
|
||||
|
||||
d3.select(node.elem)
|
||||
.select('button')
|
||||
.on('click', evt => {
|
||||
evt.stopPropagation();
|
||||
this.handleContextMenuChange(evt.clientX, evt.clientY, true, node.activity);
|
||||
});
|
||||
// .on('click', () => toggleMenu()))
|
||||
});
|
||||
|
||||
// d3.selectAll('.node.add > rect').each((node) => {
|
||||
// console.log(node)
|
||||
// })
|
||||
}
|
||||
|
||||
onConnectionCreated(connection: ConnectionModel) {
|
||||
const workflowModel = {...this.workflowModel};
|
||||
workflowModel.connections = [...workflowModel.connections, connection];
|
||||
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
rerenderTree() {
|
||||
this.applyZoom();
|
||||
this.setEnities();
|
||||
this.renderNodes();
|
||||
}
|
||||
|
||||
onConnectionDetached(connection: ConnectionModel) {
|
||||
const workflowModel = {...this.workflowModel};
|
||||
workflowModel.connections = workflowModel.connections.filter(x => !(x.sourceId == connection.sourceId && x.targetId == connection.targetId && x.outcome == connection.outcome));
|
||||
// createActivity() {
|
||||
// this.graph.setNode('node-1', this.createActivityOptions({ displayName: 'node-1', activityId: '1' }));
|
||||
// }
|
||||
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
createActivityOptions(activity: ActivityModel) {
|
||||
return {
|
||||
shape: 'rect',
|
||||
label: this.renderActivity(activity),
|
||||
rx: 5,
|
||||
ry: 5,
|
||||
labelType: 'html',
|
||||
class: 'activity',
|
||||
activity,
|
||||
};
|
||||
}
|
||||
|
||||
onAddButtonClick() {
|
||||
this.showActivityPicker();
|
||||
createOutcomeActivityOptions() {
|
||||
return { shape: 'circle', label: this.renderOutcomeButton(), labelType: 'html', class: 'add', width: 32, height: 32 };
|
||||
}
|
||||
|
||||
onOutcomeButtonClick(e: Event, outcome?: string, activityId?: string) {
|
||||
e.preventDefault();
|
||||
this.parentActivityId = activityId;
|
||||
this.parentActivityOutcome = outcome;
|
||||
this.showActivityPicker();
|
||||
renderOutcomeButton() {
|
||||
return `<svg class="h-8 w-8 text-gray-400 hover:text-blue-500 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
async onDragStartActivity(e: DragEvent, activityDisplayContext: ActivityDesignDisplayContext) {
|
||||
const activityModel = activityDisplayContext.activityModel;
|
||||
e.dataTransfer.setData("text", activityModel.activityId);
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
renderActivity(activity: ActivityModel) {
|
||||
const displayContext = this.activityDisplayContexts[activity.activityId] || undefined
|
||||
return `<div id=${`activity-${activity.activityId}`}
|
||||
class="activity border-2 border-solid border-white rounded bg-white text-left text-black text-lg hover:border-blue-600 select-none max-w-md shadow-sm relative">
|
||||
<div class="p-5">
|
||||
<div class="flex justify-between space-x-8">
|
||||
<div class="flex-shrink-0">
|
||||
${displayContext?.activityIcon || ""}
|
||||
</div>
|
||||
<div class="flex-1 font-medium leading-8">
|
||||
<p>${activity.displayName}</p>
|
||||
</div>
|
||||
<div class="context-menu-wrapper flex-shrink-0">
|
||||
<button aria-haspopup="true"
|
||||
class="w-8 h-8 inline-flex items-center justify-center text-gray-400 rounded-full bg-transparent hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100 transition ease-in-out duration-150">
|
||||
<svg class="h-6 w-6 text-gray-400" 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="5" cy="12" r="1"/>
|
||||
<circle cx="12" cy="12" r="1"/>
|
||||
<circle cx="19" cy="12" r="1"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.renderActivityBody(activity.description)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
onDragOverOutcome(e: DragEvent, outcome: string, sourceActivityId: string) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
onDropOnOutcome(e: DragEvent, outcome: string, sourceActivityId) {
|
||||
const targetActivityId = e.dataTransfer.getData('text');
|
||||
|
||||
if (sourceActivityId === targetActivityId)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const workflowModel = {...this.workflowModel};
|
||||
const targetActivityDisplayContext = this.activityDisplayContexts[targetActivityId];
|
||||
let connections = [...workflowModel.connections];
|
||||
|
||||
// Remove current connection where dropped activity is target.
|
||||
connections = connections.filter(x => x.targetId !== targetActivityId)
|
||||
|
||||
// Check if there is already a connection.
|
||||
const existingConnection = connections.find(x => x.sourceId == sourceActivityId && x.outcome == outcome);
|
||||
|
||||
if (existingConnection) {
|
||||
// Try to find a matching outcome on the dropped activity to attach the existing activity to.
|
||||
const newOutcome = targetActivityDisplayContext.outcomes.find(x => x == existingConnection.outcome) || targetActivityDisplayContext.outcomes[0];
|
||||
|
||||
existingConnection.sourceId = targetActivityId;
|
||||
existingConnection.outcome = newOutcome;
|
||||
|
||||
// Create new connection.
|
||||
const newConnection: ConnectionModel = {
|
||||
sourceId: sourceActivityId,
|
||||
targetId: targetActivityId,
|
||||
outcome: outcome
|
||||
};
|
||||
|
||||
workflowModel.connections = [...connections, newConnection];
|
||||
} else {
|
||||
// Create new connection.
|
||||
const newConnection: ConnectionModel = {
|
||||
sourceId: sourceActivityId,
|
||||
targetId: targetActivityId,
|
||||
outcome: outcome
|
||||
};
|
||||
|
||||
workflowModel.connections = [...connections, newConnection];
|
||||
}
|
||||
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
renderActivityBody(description: string | null) {
|
||||
if (!description) return '';
|
||||
return `<div class="p-6 text-gray-400 text-sm border-t border-t-solid">
|
||||
${description}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const renderedActivities = new Set<string>();
|
||||
|
||||
return (
|
||||
<Host class="workflow-canvas flex-1 flex" ref={el => this.el = el}>
|
||||
<div class="flex-1 text-gray-200">
|
||||
<div class="p-10">
|
||||
<div class="canvas select-none" ref={el => this.canvasElement = el}>
|
||||
<div class="tree">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="inline-flex flex flex-col items-center">
|
||||
<button id="start-button"
|
||||
onClick={() => this.onAddButtonClick()}
|
||||
type="button"
|
||||
class="px-6 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-green-600 hover:bg-green-500 focus:outline-none focus:border-green-700 focus:shadow-outline-green active:bg-green-700 transition ease-in-out duration-150">
|
||||
Start
|
||||
</button>
|
||||
</div>
|
||||
{this.renderTree(this.getRootActivities(), true, renderedActivities)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Host class="workflow-canvas flex-1 flex" ref={el => (this.el = el)}>
|
||||
<svg ref={el => (this.svg = el)} id="svg" style={{ height: '100vh', width: '100vw', pointerEvents: this.contextMenu.shown ? 'none' : '' }}>
|
||||
<g ref={el => (this.inner = el)} />
|
||||
</svg>
|
||||
<div
|
||||
data-transition-enter="transition ease-out duration-100"
|
||||
data-transition-enter-start="transform opacity-0 scale-95"
|
||||
data-transition-enter-end="transform opacity-100 scale-100"
|
||||
data-transition-leave="transition ease-in duration-75"
|
||||
data-transition-leave-start="transform opacity-100 scale-100"
|
||||
data-transition-leave-end="transform opacity-0 scale-95"
|
||||
class={`${this.contextMenu.shown ? '' : 'hidden'} context-menu z-10 mx-3 w-48 mt-1 rounded-md shadow-lg`}
|
||||
style={{ position: 'absolute', left: `${this.contextMenu.x}px`, top: `${this.contextMenu.y}px` }}
|
||||
ref={el =>
|
||||
registerClickOutside(this, el, () => {
|
||||
this.handleContextMenuChange(0, 0, false, null);
|
||||
})
|
||||
}
|
||||
>
|
||||
<div class="rounded-md bg-white shadow-xs" role="menu" aria-orientation="vertical" aria-labelledby="pinned-project-options-menu-0">
|
||||
<div class="py-1">
|
||||
<a
|
||||
onClick={e => this.onEditActivityClick(e)}
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
|
||||
role="menuitem"
|
||||
>
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-100" />
|
||||
<div class="py-1">
|
||||
<a
|
||||
onClick={e => this.onDeleteActivityClick(e)}
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
|
||||
role="menuitem"
|
||||
>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div innerHTML={<ActivityIcon/>}/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
renderTree(activities: Array<ActivityModel>, isRoot: boolean, renderedActivities: Set<string>): any {
|
||||
const list = activities.filter(x => !renderedActivities.has(x.activityId));
|
||||
const cssClass = isRoot ? "root" : undefined;
|
||||
|
||||
for (const activity of list)
|
||||
renderedActivities.add(activity.activityId);
|
||||
|
||||
if (list.length == 0)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<ul class={cssClass}>
|
||||
{activities.map(x => {
|
||||
const activityId = x.activityId;
|
||||
const children = getChildActivities(this.workflowModel, activityId);
|
||||
const displayContext = this.activityDisplayContexts[activityId];
|
||||
|
||||
return <li key={x.activityId}>
|
||||
<div class="inline-flex flex flex-col items-center">
|
||||
{isRoot ? this.renderOutcomeButton(`start-button-plus-${x.activityId}`, '', activityId, e => this.onOutcomeButtonClick(e, null, activityId)) : undefined}
|
||||
{this.renderActivity(displayContext)}
|
||||
{this.renderOutcomeButtons(displayContext)}
|
||||
</div>
|
||||
{children.length > 0 ? this.renderTree(children, false, renderedActivities) : undefined}
|
||||
</li>;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderOutcomeButtons(displayContext: ActivityDesignDisplayContext) {
|
||||
const activityId = displayContext.activityModel.activityId;
|
||||
|
||||
return (
|
||||
<div class="flex flex-row space-x-6">
|
||||
{displayContext.outcomes.map(x => this.renderOutcomeButton(`${activityId}-${x}`, x, activityId, e => this.onOutcomeButtonClick(e, x, activityId)))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderOutcomeButton(id: string, outcome: string, activityId: string, clickHandler: (e: MouseEvent) => void) {
|
||||
return (
|
||||
<div class="my-6 flex flex-col items-center">
|
||||
{outcome && outcome.length > 0 ? <div
|
||||
class="mb-4 relative z-10 px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-cool-gray-100 text-cool-gray-800 capitalize">{outcome}</div> : undefined}
|
||||
<a key={id} id={id} href="#" onClick={e => clickHandler(e)}
|
||||
onDragOver={e => this.onDragOverOutcome(e, outcome, activityId)}
|
||||
onDrop={e => this.onDropOnOutcome(e, outcome, activityId)}
|
||||
>
|
||||
<svg class="h-8 w-8 text-gray-400 hover:text-blue-500 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderActivity(displayContext: ActivityDesignDisplayContext) {
|
||||
return <elsa-designer-tree-activity displayContext={displayContext} draggable={true}
|
||||
onDragStart={e => this.onDragStartActivity(e, displayContext)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export class ElsaWorkflowDefinitionEditor {
|
|||
this.networkError = null;
|
||||
|
||||
// A small hack to make sure JS Plumb is cleaned up before HTML elements get removed.
|
||||
await this.designer.destroyJsPlumb();
|
||||
// await this.designer.destroyJsPlumb();
|
||||
|
||||
try {
|
||||
const workflowDefinition = await client.workflowDefinitionsApi.import(this.workflowDefinition.definitionId, file);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
export const ActivityIcon = props => {
|
||||
const color = props.color || 'light-blue';
|
||||
return (
|
||||
<span class={`rounded-lg inline-flex p-3 bg-${color}-50 text-${color}-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-${color}-50 text-${color}-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/> <polyline points="21 12 17 12 14 20 10 4 7 12 3 12"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
export const BreakIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2"/>
|
||||
<path d="M7 12h14l-3 -3m0 6l3 -3"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
export const CogIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
export const CorrelateIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" 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="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"/>
|
||||
<path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
export const EraseIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" 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="M19 19h-11l-4 -4a1 1 0 0 1 0 -1.41l10 -10a1 1 0 0 1 1.41 0l5 5a1 1 0 0 1 0 1.41l-9 9"/>
|
||||
<path d="M18 12.3l-6.3 -6.3"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
export const FinishIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
|
||||
<line x1="4" y1="22" x2="4" y2="15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export const ForkIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6 transform rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="18" r="2"/>
|
||||
|
|
@ -11,5 +11,5 @@ export const ForkIcon = props =>
|
|||
<path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2"/>
|
||||
<line x1="12" y1="12" x2="12" y2="16"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const HttpEndpointIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const IfIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" 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>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
export const InterruptTriggerIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M8 16v-4a4 4 0 0 1 8 0v4"/>
|
||||
<path d="M3 12h1M12 3v1M20 12h1M5.6 5.6l.7 .7M18.4 5.6l-.7 .7"/>
|
||||
<rect x="6" y="16" width="12" height="4" rx="1"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export const JoinIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="18" r="2"/>
|
||||
|
|
@ -11,5 +11,5 @@ export const JoinIcon = props =>
|
|||
<path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2"/>
|
||||
<line x1="12" y1="12" x2="12" y2="16"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
export const LoopIcon = props =>
|
||||
(
|
||||
<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
`<span class="rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" 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"/>
|
||||
<path d="M11 11l1 -1v4"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
export const ReadLineIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" 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="5 7 10 12 5 17"/>
|
||||
<line x1="13" y1="17" x2="19" y2="17"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
export const RedirectIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" 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="6" cy="19" r="2"/>
|
||||
<circle cx="18" cy="5" r="2"/>
|
||||
<path d="M12 19h4.5a3.5 3.5 0 0 0 0 -7h-8a3.5 3.5 0 0 1 0 -7h3.5"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export const RunWorkflowIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" 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"/>
|
||||
|
|
@ -10,5 +10,5 @@ export const RunWorkflowIcon = props =>
|
|||
<path d="M21 11v-3a2 2 0 0 0 -2 -2h-6l3 3m0 -6l-3 3"/>
|
||||
<path d="M3 13v3a2 2 0 0 0 2 2h6l-3 -3m0 6l3 -3"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
export const ScriptIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" 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="M15 21h-9a3 3 0 0 1 -3 -3v-1h10v2a2 2 0 0 0 4 0v-14a2 2 0 1 1 2 2h-2m2 -4h-11a3 3 0 0 0 -3 3v11"/>
|
||||
<line x1="9" y1="7" x2="13" y2="7"/>
|
||||
<line x1="9" y1="11" x2="13" y2="11"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const SendEmailIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const SendHttpRequestIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
export const SendSignalIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="2"/>
|
||||
<path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const SignalReceivedIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/> <polyline points="21 12 17 12 14 20 10 4 7 12 3 12"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
export const SwitchIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-green-50 text-green-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" 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"/>
|
||||
|
|
@ -11,5 +11,5 @@ export const SwitchIcon = props =>
|
|||
<polyline points="15 19 19 19 19 15"/>
|
||||
<line x1="5" y1="5" x2="19" y2="19"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const TimerIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-rose-50 text-rose-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const WriteHttpResponseIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="${`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
export const WriteLineIcon = props =>
|
||||
(
|
||||
<span class={`rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white`}>
|
||||
`<span class="rounded-lg inline-flex p-3 bg-light-blue-50 text-light-blue-700 ring-4 ring-white">
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>`
|
||||
);
|
||||
|
|
|
|||
|
|
@ -145,9 +145,8 @@ export class ElsaActivityPickerModal {
|
|||
{displayContexts.map(displayContext => (
|
||||
<a href="#" onClick={e => this.onActivityClick(e, displayContext.activityDescriptor)} class="relative rounded group p-6 focus-within:ring-2 focus-within:ring-inset focus-within:ring-blue-500">
|
||||
<div class="flex space-x-10">
|
||||
<div class="flex-0">
|
||||
<div>
|
||||
{displayContext.activityIcon}
|
||||
<div class="flex flex-0 items-center">
|
||||
<div innerHTML={displayContext.activityIcon}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 mt-2">
|
||||
|
|
|
|||
Loading…
Reference in a new issue