Fix (workaround) re-render of tree
This commit is contained in:
parent
21ca097c70
commit
d38cac9ded
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, Host, h, Prop, State, Event, EventEmitter, Watch } from '@stencil/core';
|
||||
import { Map, addConnection, findActivity, getChildActivities, getInboundConnections, getOutboundConnections, removeActivity } from '../../../../utils/utils';
|
||||
import {Component, Host, h, Prop, State, Event, EventEmitter, Watch} from '@stencil/core';
|
||||
import {Map, addConnection, findActivity, getChildActivities, getInboundConnections, getOutboundConnections, removeActivity} from '../../../../utils/utils';
|
||||
import {
|
||||
ActivityDescriptor,
|
||||
ActivityDesignDisplayContext,
|
||||
|
|
@ -10,14 +10,14 @@ import {
|
|||
WorkflowModel,
|
||||
WorkflowPersistenceBehavior,
|
||||
} from '../../../../models';
|
||||
import { eventBus } from '../../../../services/event-bus';
|
||||
import {eventBus} from '../../../../services/event-bus';
|
||||
import jsPlumb from 'jsplumb';
|
||||
import * as d3 from 'd3';
|
||||
import dagreD3 from 'dagre-d3';
|
||||
import { registerClickOutside } from 'stencil-click-outside';
|
||||
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',
|
||||
|
|
@ -26,8 +26,8 @@ import { ActivityIcon } from '../../../icons/activity-icon';
|
|||
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>;
|
||||
@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;
|
||||
|
|
@ -35,7 +35,7 @@ export class ElsaWorkflowDesigner {
|
|||
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 };
|
||||
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({});
|
||||
|
|
@ -122,7 +122,7 @@ export class ElsaWorkflowDesigner {
|
|||
};
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -137,7 +137,7 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,10 +153,10 @@ export class ElsaWorkflowDesigner {
|
|||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -172,8 +172,8 @@ export class ElsaWorkflowDesigner {
|
|||
}
|
||||
|
||||
addConnection(sourceActivityId: string, targetActivityId: string, outcome: string) {
|
||||
const workflowModel = { ...this.workflowModel };
|
||||
const connection: ConnectionModel = { sourceId: sourceActivityId, targetId: targetActivityId, outcome: outcome };
|
||||
const workflowModel = {...this.workflowModel};
|
||||
const connection: ConnectionModel = {sourceId: sourceActivityId, targetId: targetActivityId, outcome: outcome};
|
||||
workflowModel.connections.push(connection);
|
||||
this.updateWorkflowModel(workflowModel);
|
||||
this.parentActivityId = null;
|
||||
|
|
@ -181,11 +181,11 @@ export class ElsaWorkflowDesigner {
|
|||
}
|
||||
|
||||
updateActivity(activity: ActivityModel) {
|
||||
let workflowModel = { ...this.workflowModel };
|
||||
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 });
|
||||
this.updateWorkflowModel({...workflowModel, activities: activities});
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
|
|
@ -196,8 +196,9 @@ export class ElsaWorkflowDesigner {
|
|||
this.svgD3Selected = d3.select(this.svg);
|
||||
this.innerD3Selected = d3.select(this.inner);
|
||||
this.applyZoom();
|
||||
this.setEnities();
|
||||
this.renderNodes();
|
||||
this.setEntities();
|
||||
|
||||
setTimeout(() => this.renderNodes(), 50);
|
||||
}
|
||||
|
||||
componentWillRender() {
|
||||
|
|
@ -214,7 +215,7 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
const displayContext: ActivityDesignDisplayContext = {
|
||||
activityModel: model,
|
||||
activityIcon: <ActivityIcon color={color} />,
|
||||
activityIcon: <ActivityIcon color={color}/>,
|
||||
bodyDisplay: bodyDisplay,
|
||||
outcomes: [...model.outcomes],
|
||||
};
|
||||
|
|
@ -222,10 +223,12 @@ export class ElsaWorkflowDesigner {
|
|||
eventBus.emit(EventTypes.ActivityDesignDisplaying, this, displayContext);
|
||||
displayContexts[model.activityId] = displayContext;
|
||||
}
|
||||
|
||||
this.activityDisplayContexts = displayContexts;
|
||||
}
|
||||
|
||||
componentDidRender() {}
|
||||
componentDidRender() {
|
||||
}
|
||||
|
||||
showActivityPicker() {
|
||||
eventBus.emit(EventTypes.ShowActivityPicker);
|
||||
|
|
@ -236,7 +239,7 @@ export class ElsaWorkflowDesigner {
|
|||
}
|
||||
|
||||
removeActivity(activity: ActivityModel) {
|
||||
let workflowModel = { ...this.workflowModel };
|
||||
let workflowModel = {...this.workflowModel};
|
||||
const incomingConnections = getInboundConnections(workflowModel, activity.activityId);
|
||||
const outgoingConnections = getOutboundConnections(workflowModel, activity.activityId);
|
||||
|
||||
|
|
@ -255,7 +258,7 @@ export class ElsaWorkflowDesigner {
|
|||
|
||||
applyZoom() {
|
||||
this.zoom = d3.zoom().on('zoom', event => {
|
||||
const { transform } = event;
|
||||
const {transform} = event;
|
||||
this.innerD3Selected.attr('transform', transform);
|
||||
this.zoomParams = {
|
||||
x: transform.x,
|
||||
|
|
@ -266,7 +269,7 @@ export class ElsaWorkflowDesigner {
|
|||
this.svgD3Selected.call(this.zoom);
|
||||
}
|
||||
|
||||
setEnities() {
|
||||
setEntities() {
|
||||
this.graph = new dagreD3.graphlib.Graph().setGraph({});
|
||||
|
||||
const rootActivities = this.getRootActivities();
|
||||
|
|
@ -284,15 +287,17 @@ export class ElsaWorkflowDesigner {
|
|||
this.graph.setEdge('start', `${activity.activityId}/start`, {
|
||||
arrowhead: 'undirected',
|
||||
});
|
||||
this.graph.setNode(`${activity.activityId}/start`, { shape: 'rect', activity, label: this.renderOutcomeButton(), labelType: 'html', class: 'add' });
|
||||
this.graph.setEdge(`${activity.activityId}/start`, activity.activityId, { arrowhead: 'undirected' });
|
||||
this.graph.setNode(`${activity.activityId}/start`, {shape: 'rect', activity, label: this.renderOutcomeButton(), labelType: 'html', class: 'add'});
|
||||
this.graph.setEdge(`${activity.activityId}/start`, activity.activityId, {arrowhead: 'undirected'});
|
||||
});
|
||||
|
||||
this.workflowModel.activities.forEach(activity => {
|
||||
this.graph.setNode(activity.activityId, this.createActivityOptions(activity));
|
||||
const displayContext = this.activityDisplayContexts[activity.activityId] || undefined;
|
||||
const outcomes = !!displayContext ? displayContext.outcomes : activity.outcomes || [];
|
||||
|
||||
activity.outcomes.forEach(outcome => {
|
||||
this.graph.setNode(`${activity.activityId}/${outcome}`, { shape: 'rect', outcome, activity, label: this.renderOutcomeButton(), labelType: 'html', class: 'add' });
|
||||
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 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}</p>`,
|
||||
labelpos: 'c',
|
||||
|
|
@ -302,8 +307,8 @@ export class ElsaWorkflowDesigner {
|
|||
});
|
||||
});
|
||||
|
||||
this.workflowModel.connections.forEach(({ sourceId, targetId, outcome }) => {
|
||||
this.graph.setEdge(`${sourceId}/${outcome}`, targetId, { arrowhead: 'undirected' });
|
||||
this.workflowModel.connections.forEach(({sourceId, targetId, outcome}) => {
|
||||
this.graph.setEdge(`${sourceId}/${outcome}`, targetId, {arrowhead: 'undirected'});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -357,17 +362,17 @@ export class ElsaWorkflowDesigner {
|
|||
}
|
||||
});
|
||||
d3.select(node.elem)
|
||||
.select('button')
|
||||
.on('click', evt => {
|
||||
evt.stopPropagation();
|
||||
this.handleContextMenuChange(evt.clientX, evt.clientY, true, node.activity);
|
||||
});
|
||||
.select('button')
|
||||
.on('click', evt => {
|
||||
evt.stopPropagation();
|
||||
this.handleContextMenuChange(evt.clientX, evt.clientY, true, node.activity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
rerenderTree() {
|
||||
this.applyZoom();
|
||||
this.setEnities();
|
||||
this.setEntities();
|
||||
this.renderNodes();
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +389,7 @@ export class ElsaWorkflowDesigner {
|
|||
}
|
||||
|
||||
createOutcomeActivityOptions() {
|
||||
return { shape: 'circle', label: this.renderOutcomeButton(), labelType: 'html', class: 'add', width: 32, height: 32 };
|
||||
return {shape: 'circle', label: this.renderOutcomeButton(), labelType: 'html', class: 'add', width: 32, height: 32};
|
||||
}
|
||||
|
||||
renderOutcomeButton() {
|
||||
|
|
@ -397,30 +402,30 @@ export class ElsaWorkflowDesigner {
|
|||
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>`;
|
||||
<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>`;
|
||||
}
|
||||
|
||||
renderActivityBody(description: string | null) {
|
||||
|
|
@ -441,8 +446,8 @@ ${this.renderActivityBody(activity.description)}
|
|||
render() {
|
||||
return (
|
||||
<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 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"
|
||||
|
|
@ -451,8 +456,8 @@ ${this.renderActivityBody(activity.description)}
|
|||
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` }}
|
||||
class={`${this.contextMenu.shown ? '' : 'hidden'} context-menu z-10 mx-3 w-48 mt-1 rounded-md shadow-lg absolute`}
|
||||
style={{left: `${this.contextMenu.x}px`, top: `${this.contextMenu.y}px`}}
|
||||
ref={el =>
|
||||
registerClickOutside(this, el, () => {
|
||||
this.handleContextMenuChange(0, 0, false, null);
|
||||
|
|
@ -470,7 +475,7 @@ ${this.renderActivityBody(activity.description)}
|
|||
Edit
|
||||
</a>
|
||||
</div>
|
||||
<div class="border-t border-gray-100" />
|
||||
<div class="border-t border-gray-100"/>
|
||||
<div class="py-1">
|
||||
<a
|
||||
onClick={e => this.onDeleteActivityClick(e)}
|
||||
|
|
@ -483,7 +488,6 @@ ${this.renderActivityBody(activity.description)}
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div innerHTML={<ActivityIcon />} />
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue