in src/OrchardCore.Modules/OrchardCore.Workflows/Assets/Scripts/workflow-editor.ts [13:233]
constructor(protected container: HTMLElement, protected workflowType: Workflows.WorkflowType, private deleteActivityPrompt: string, private localId: string, loadLocalState: boolean) {
super(container, workflowType);
const self = this;
jsPlumb.ready(() => {
jsPlumb.importDefaults(this.getDefaults());
const plumber = this.createJsPlumbInstance();
// Listen for new connections.
plumber.bind('connection', function (connInfo, originalEvent) {
const connection: Connection = connInfo.connection;
const outcome: Workflows.Outcome = connection.getParameters().outcome;
const label: any = connection.getOverlay('label');
label.setLabel(outcome.displayName);
});
let activityElements = this.getActivityElements();
var areEqualOutcomes = function (outcomes1: Workflows.Outcome[], outcomes2: Workflows.Outcome[]): boolean {
if (outcomes1.length != outcomes2.length) {
return false;
}
for (let i = 0; i < outcomes1.length; i++) {
const outcome1 = outcomes1[i];
const outcome2 = outcomes2[i];
if (outcome1.name != outcome2.displayName || outcome1.displayName != outcome2.displayName) {
return false;
}
}
return true;
}
// Suspend drawing and initialize.
plumber.batch(() => {
var serverworkflowType: Workflows.WorkflowType = this.workflowType;
var workflowId: number = this.workflowType.id;
if (loadLocalState) {
const localState: Workflows.WorkflowType = this.loadLocalState();
if (localState) {
this.workflowType = localState;
}
}
activityElements.each((index, activityElement) => {
const $activityElement = $(activityElement);
const activityId = $activityElement.data('activity-id');
const isDeleted = this.workflowType.removedActivities.indexOf(activityId) > -1;
if (isDeleted) {
$activityElement.remove();
return;
}
let activity = this.getActivity(activityId);
const serverActivity = this.getActivity(activityId, serverworkflowType.activities);
// Update the activity's visual state.
if (loadLocalState) {
if (activity == null) {
// This is a newly added activity not yet added to local state.
activity = serverActivity;
this.workflowType.activities.push(activity);
activity.x = 50;
activity.y = 50;
}
else {
// The available outcomes might have changed when editing an activity,
// so we need to check for that and update the client's activity outcomes if so.
const sameOutcomes = areEqualOutcomes(activity.outcomes, serverActivity.outcomes);
if (!sameOutcomes) {
activity.outcomes = serverActivity.outcomes;
}
$activityElement
.css({ left: activity.x, top: activity.y })
.toggleClass('activity-start', activity.isStart)
.data('activity-start', activity.isStart);
}
}
// Make the activity draggable.
plumber.draggable(activityElement, {
grid: [10, 10],
containment: true,
start: (args: any) => {
this.dragStart = { left: args.e.screenX, top: args.e.screenY };
},
stop: (args: any) => {
this.hasDragged = this.dragStart.left != args.e.screenX || this.dragStart.top != args.e.screenY;
this.updateCanvasHeight();
}
});
// Configure the activity as a target.
plumber.makeTarget(activityElement, {
dropOptions: { hoverClass: 'hover' },
anchor: 'Continuous',
endpoint: ['Blank', { radius: 8 }]
});
// Add source endpoints.
for (let outcome of activity.outcomes) {
const sourceEndpointOptions = this.getSourceEndpointOptions(activity, outcome);
plumber.addEndpoint(activityElement, { connectorOverlays: [['Label', { label: outcome.displayName, cssClass: 'connection-label' }]] }, sourceEndpointOptions);
}
});
// Connect activities.
this.updateConnections(plumber);
// Re-query the activity elements.
activityElements = this.getActivityElements();
// Make all activity elements visible.
activityElements.show();
this.updateCanvasHeight();
});
// Initialize popovers.
activityElements.popover({
trigger: 'manual',
html: true,
content: function () {
const activityElement = $(this);
const $content: JQuery<Element> = activityElement.find('.activity-commands').clone().show();
const startButton = $content.find('.activity-start-action');
const isStart = activityElement.data('activity-start') === true;
const activityId: number = activityElement.data('activity-id');
startButton.attr('aria-pressed', activityElement.data('activity-start'));
startButton.toggleClass('active', isStart);
$content.on('click', '.activity-start-action', e => {
e.preventDefault();
const button = $(e.currentTarget);
button.button('toggle');
const isStart = button.is('.active');
activityElement.data('activity-start', isStart);
activityElement.toggleClass('activity-start', isStart);
});
$content.on('click', '.activity-delete-action', e => {
e.preventDefault();
// TODO: The prompts are really annoying. Consider showing some sort of small message balloon somewhere to undo the action instead.
//if (!confirm(self.deleteActivityPrompt)) {
// return;
//}
self.workflowType.removedActivities.push(activityId);
plumber.remove(activityElement);
activityElement.popover('dispose');
});
$content.on('click', '[data-persist-workflow]', e => {
self.saveLocalState();
});
return $content.get(0);
}
});
$(container).on('click', '.activity', e => {
if (this.hasDragged) {
this.hasDragged = false;
return;
}
// if any other popovers are visible, hide them
if (this.isPopoverVisible) {
activityElements.popover('hide');
}
const sender = $(e.currentTarget);
sender.popover('show');
// handle clicking on the popover itself.
$('.popover').off('click').on('click', e2 => {
e2.stopPropagation();
})
e.stopPropagation();
this.isPopoverVisible = true;
});
$(container).on('dblclick', '.activity', e => {
const sender = $(e.currentTarget);
const hasEditor = sender.data('activity-has-editor');
if (hasEditor) {
this.saveLocalState();
sender.find('.activity-edit-action').get(0).click();
}
});
// Hide all popovers when clicking anywhere but on an activity.
$('body').on('click', e => {
activityElements.popover('hide');
this.isPopoverVisible = false;
});
// Save local changes if the event target has the 'data-persist-workflow' attribute.
$('body').on('click', '[data-persist-workflow]', e => {
this.saveLocalState();
});
this.jsPlumbInstance = plumber;
});
}