Implement ID change handling

This commit is contained in:
Sipke Schoorstra 2022-07-26 11:29:44 +02:00
parent 059b432065
commit be92cc3d76
7 changed files with 119 additions and 77 deletions

View file

@ -8,16 +8,16 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { ActivityDefinition, ActivityDefinitionPropsUpdatedArgs, ActivityDefinitionSummary, ActivityDefinitionUpdatedArgs } from "./modules/activity-definitions/models";
import { ActivityDriverRegistry, ContainerActivityComponent } from "./services";
import { ActionDefinition, ActionInvokedArgs, Activity, ActivityDeletedArgs, ActivitySelectedArgs, ChildActivitySelectedArgs, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs, IntellisenseContext, SelectListItem, TabChangedArgs, TabDefinition, Variable, WorkflowInstance, WorkflowInstanceSummary } from "./models";
import { ActivityIdUpdatedArgs, ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./modules/workflow-definitions/components/activity-properties-editor";
import { ActivityUpdatedArgs, DeleteActivityRequestedArgs } from "./modules/workflow-definitions/components/activity-properties-editor";
import { PublishClickedArgs } from "./modules/activity-definitions/components/publish-button";
import { Button } from "./components/shared/button-group/models";
import { AddActivityArgs, UpdateActivityArgs } from "./components/designer/canvas/canvas";
import { AddActivityArgs, RenameActivityArgs, UpdateActivityArgs } from "./components/designer/canvas/canvas";
import { ActivityInputContext } from "./services/node-input-driver";
import { ContextMenuAnchorPoint, MenuItem, MenuItemGroup } from "./components/shared/context-menu/models";
import { DropdownButtonItem, DropdownButtonOrigin } from "./components/shared/dropdown-button/models";
import { Flowchart, FlowchartNavigationItem } from "./modules/flowchart/models";
import { Graph } from "@antv/x6";
import { AddActivityArgs as AddActivityArgs1, UpdateActivityArgs as UpdateActivityArgs1 } from "./components/designer/canvas/canvas";
import { AddActivityArgs as AddActivityArgs1, RenameActivityArgs as RenameActivityArgs1, UpdateActivityArgs as UpdateActivityArgs1 } from "./components/designer/canvas/canvas";
import { ActivityNodeShape } from "./modules/flowchart/shapes";
import { ExpressionChangedArs } from "./components/designer/input-control-switch/input-control-switch";
import { CreateLabelEventArgs, DeleteLabelEventArgs, Label, UpdateLabelEventArgs } from "./modules/labels/models";
@ -71,6 +71,7 @@ export namespace Components {
"importGraph": (root: Activity) => Promise<void>;
"interactiveMode": boolean;
"newRoot": () => Promise<Activity>;
"renameActivity": (args: RenameActivityArgs) => Promise<void>;
"reset": () => Promise<void>;
"updateActivity": (args: UpdateActivityArgs) => Promise<void>;
"updateLayout": () => Promise<void>;
@ -118,6 +119,7 @@ export namespace Components {
"import": (root: Activity) => Promise<void>;
"interactiveMode": boolean;
"newRoot": () => Promise<Activity>;
"renameActivity": (args: RenameActivityArgs) => Promise<void>;
"reset": () => Promise<void>;
"updateActivity": (args: UpdateActivityArgs) => Promise<void>;
"updateLayout": () => Promise<void>;
@ -717,7 +719,6 @@ declare namespace LocalJSX {
}
interface ElsaActivityPropertiesEditor {
"activity"?: Activity;
"onActivityIdUpdated"?: (event: CustomEvent<ActivityIdUpdatedArgs>) => void;
"onActivityUpdated"?: (event: CustomEvent<ActivityUpdatedArgs>) => void;
"onDeleteActivityRequested"?: (event: CustomEvent<DeleteActivityRequestedArgs>) => void;
"variables"?: Array<Variable>;

View file

@ -11,6 +11,13 @@ export interface AddActivityArgs {
export interface UpdateActivityArgs {
id: string;
originalId: string;
activity: Activity;
}
export interface RenameActivityArgs {
originalId: string;
newId: string;
activity: Activity;
}
@ -39,6 +46,11 @@ export class Canvas {
await this.root.updateActivity(args);
}
@Method()
public async renameActivity(args: RenameActivityArgs): Promise<void> {
await this.root.renameActivity(args);
}
@Method()
public async updateLayout(): Promise<void> {
await this.root.updateLayout();

View file

@ -84,11 +84,6 @@ export class Editor {
const parentActivityId = parentActivity.id;
}
@Listen('activityDeleted')
private async handleActivityDeleted(e: CustomEvent<ActivityDeletedArgs>) {
//this.selectedActivity = this.getCurrentContainer();
}
@Listen('graphUpdated')
private async handleGraphUpdated(e: CustomEvent<GraphUpdatedArgs>) {
this.updateModelDebounced();
@ -160,8 +155,7 @@ export class Editor {
return <elsa-activity-properties-editor
activity={this.selectedActivity}
variables={this.activityDefinitionState.variables}
onActivityUpdated={e => this.onActivityUpdated(e)}
onActivityIdUpdated={e => this.onActivityIdUpdated(e)}/>;
onActivityUpdated={e => this.onActivityUpdated(e)}/>;
}
private getActivityDefinitionInternal = async (): Promise<ActivityDefinition> => {
@ -220,26 +214,17 @@ export class Editor {
private onZoomToFit = async () => await this.canvas.zoomToFit()
private onActivityUpdated = async (e: CustomEvent<ActivityUpdatedArgs>) => {
const updatedActivity = e.detail.activity;
await this.canvas.updateActivity({activity: updatedActivity, id: updatedActivity.id});
await this.canvas.updateActivity({
id: e.detail.newId,
originalId: e.detail.originalId,
activity: e.detail.activity,
});
await this.updateModel();
this.emitActivityChangedDebounced({...e.detail, workflowEditor: this.el});
this.saveChangesDebounced();
}
private onActivityIdUpdated = (e: CustomEvent<ActivityIdUpdatedArgs>) => {
// const originalId = e.detail.originalId;
// const newId = e.detail.newId;
// const workflowPath = this.currentWorkflowPath;
// const item = workflowPath.find(x => x.activityId == originalId);
//
// if (!item)
// return;
//
// item.activityId = newId;
// this.createNodeMap(this.workflowDefinition);
}
private onActivityDefinitionPropsUpdated = (e: CustomEvent<ActivityDefinitionPropsUpdatedArgs>) => {
this.updateModelDebounced();
this.saveChangesDebounced();

View file

@ -5,7 +5,7 @@ import {Edge, Graph, Model, Node, NodeView, Point} from '@antv/x6';
import './shapes';
import './ports';
import {ActivityNodeShape} from './shapes';
import {AddActivityArgs, UpdateActivityArgs} from '../../components/designer/canvas/canvas';
import {AddActivityArgs, RenameActivityArgs, UpdateActivityArgs} from '../../components/designer/canvas/canvas';
import {Activity, ActivityDeletedArgs, ActivityDescriptor, ActivitySelectedArgs, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs} from '../../models';
import {createGraph} from './graph-factory';
import {Connection, Flowchart, FlowchartModel, FlowchartNavigationItem} from './models';
@ -140,7 +140,42 @@ export class FlowchartComponent implements ContainerActivityComponent {
@Method()
async updateActivity(args: UpdateActivityArgs) {
const nodeId = args.id;
const activityId = args.id;
const originalId = args.originalId;
const nodeId = originalId;
const activity = args.activity;
const node = this.graph.getNodes().find(x => x.id == nodeId) as ActivityNodeShape;
if (!!node) {
// Update the node's data with the activity.
node.setData(activity, {overwrite: true});
// Updating the node's activity property to trigger a rerender.
node.activity = activity;
// If the ID of the activity changed, we need to update connection references (X6 stores deep copies of data).
if (activityId !== originalId)
this.syncEdgeData(nodeId, activity);
}
// If the ID of the activity changed, we need to update the workflow path model and lookup.
if (activityId !== originalId) {
const workflowPath = [...this.currentPath];
const item = workflowPath.find(x => x.activityId == originalId);
if (!!item) {
item.activityId = activityId;
this.currentPath = workflowPath;
}
this.updateLookups();
}
}
@Method()
public async renameActivity(args: RenameActivityArgs) {
const nodeId = args.originalId;
const activity = args.activity;
const node = this.graph.getNodes().find(x => x.id == nodeId) as ActivityNodeShape;
@ -401,15 +436,6 @@ export class FlowchartComponent implements ContainerActivityComponent {
}
};
private getCurrentScope(): Activity {
const currentItem = this.currentPath.length > 0 ? this.currentPath[this.currentPath.length - 1] : null;
if (!currentItem)
return this.activity;
return this.activityLookup[currentItem.activityId];
}
private getCurrentContainerInternal(): Flowchart {
const currentItem = this.currentPath.length > 0 ? this.currentPath[this.currentPath.length - 1] : null;

View file

@ -16,6 +16,8 @@ import {isNullOrWhitespace} from "../../../utils";
import descriptorsStore from "../../../data/descriptors-store";
export interface ActivityUpdatedArgs {
originalId: string;
newId: string;
activity: Activity;
activityDescriptor: ActivityDescriptor;
propertyName?: string;
@ -49,7 +51,6 @@ export class ActivityPropertiesEditor {
@Prop() variables: Array<Variable> = [];
@Event() activityUpdated: EventEmitter<ActivityUpdatedArgs>;
@Event() activityIdUpdated: EventEmitter<ActivityIdUpdatedArgs>;
@Event() deleteActivityRequested: EventEmitter<DeleteActivityRequestedArgs>;
@State() private selectedTabIndex: number = 0;
@ -65,16 +66,26 @@ export class ActivityPropertiesEditor {
componentWillRender() {
const activity = this.activity;
const activityId = activity.id;
const activityDescriptor = this.findActivityDescriptor();
const title = activityDescriptor?.displayName ?? activityDescriptor?.activityType ?? 'Unknown Activity';
const driverRegistry = this.inputDriverRegistry;
const onInputChanged = (inputDescriptor: InputDescriptor) => this.activityUpdated.emit({
newId: activityId,
originalId: activityId,
activity,
activityDescriptor,
propertyName: camelCase(inputDescriptor.name),
propertyDescriptor: inputDescriptor
});
const renderInputPropertyContexts: Array<RenderActivityInputContext> = activityDescriptor.inputs.map(inputDescriptor => {
const renderInputContext: ActivityInputContext = {
node: activity,
nodeDescriptor: activityDescriptor,
inputDescriptor,
notifyInputChanged: () => this.activityUpdated.emit({activity, activityDescriptor, propertyName: camelCase(inputDescriptor.name), propertyDescriptor: inputDescriptor}),
notifyInputChanged: () => onInputChanged(inputDescriptor),
inputChanged: (v, s) => this.onInputPropertyEditorChanged(inputDescriptor, v, s)
};
@ -163,12 +174,18 @@ export class ActivityPropertiesEditor {
activity.id = newId;
this.activityIdUpdated.emit({ activity, activityDescriptor, originalId, newId: activity.id });
this.activityUpdated.emit({activity, activityDescriptor, propertyName: 'id', propertyDescriptor: inputDescriptor});
this.activityUpdated.emit({
newId: newId,
originalId: originalId,
activity,
activityDescriptor,
propertyName: 'id',
propertyDescriptor: inputDescriptor
});
}
private onActivityDisplayTextChanged(e: any) {
const activity = this.activity;
const activity: Activity = this.activity;
const inputElement = e.target as HTMLInputElement;
activity.metadata = {
@ -177,11 +194,19 @@ export class ActivityPropertiesEditor {
};
const activityDescriptor = this.findActivityDescriptor();
this.activityUpdated.emit({activity, activityDescriptor});
const activityId = activity.id;
this.activityUpdated.emit({
newId: activityId,
originalId: activityId,
activity,
activityDescriptor
});
}
private onInputPropertyEditorChanged = (inputDescriptor: InputDescriptor, propertyValue: any, syntax: string) => {
const activity = this.activity;
const activityId = activity.id;
const propertyName = inputDescriptor.name;
const activityDescriptor = this.findActivityDescriptor();
const camelCasePropertyName = camelCase(propertyName);
@ -194,11 +219,19 @@ export class ActivityPropertiesEditor {
}
};
this.activityUpdated.emit({activity, activityDescriptor, propertyName: camelCasePropertyName, propertyDescriptor: inputDescriptor});
this.activityUpdated.emit({
newId: activityId,
originalId: activityId,
activity,
activityDescriptor,
propertyName: camelCasePropertyName,
propertyDescriptor: inputDescriptor
});
}
private onOutputPropertyEditorChanged = (outputDescriptor: OutputDescriptor, variableName: string) => {
const activity = this.activity;
const activityId = activity.id;
const propertyName = outputDescriptor.name;
const activityDescriptor = this.findActivityDescriptor();
const camelCasePropertyName = camelCase(propertyName);
@ -212,11 +245,16 @@ export class ActivityPropertiesEditor {
activity[camelCasePropertyName] = property;
this.activityUpdated.emit({activity, activityDescriptor, propertyName: camelCasePropertyName, propertyDescriptor: outputDescriptor});
this.activityUpdated.emit({
newId: activityId,
originalId: activityId,
activity,
activityDescriptor,
propertyName: camelCasePropertyName,
propertyDescriptor: outputDescriptor
});
}
private onDeleteActivity = () => this.deleteActivityRequested.emit({activity: this.activity});
private renderCommonTab = () => {
const {activity,} = this.renderContext;
const activityId = activity.id;

View file

@ -79,14 +79,8 @@ export class WorkflowDefinitionEditor {
@Listen('childActivitySelected')
private async handleChildActivitySelected(e: CustomEvent<ChildActivitySelectedArgs>) {
const {parentActivity, childActivity, port} = e.detail;
const {childActivity} = e.detail;
this.selectedActivity = childActivity;
const parentActivityId = parentActivity.id;
}
@Listen('activityDeleted')
private async handleActivityDeleted(e: CustomEvent<ActivityDeletedArgs>) {
//this.selectedActivity = this.getCurrentContainer();
}
@Listen('graphUpdated')
@ -122,11 +116,6 @@ export class WorkflowDefinitionEditor {
@Method()
async updateWorkflowDefinition(workflowDefinition: WorkflowDefinition): Promise<void> {
this.workflowDefinitionState = workflowDefinition;
// if (this.currentWorkflowPath.length == 0) {
// this.currentWorkflowPath = [{activityId: workflowDefinition.root.id, portName: null, index: 0}];
// this.selectedActivity = this.getCurrentContainer();
// }
}
@Method()
@ -167,8 +156,7 @@ export class WorkflowDefinitionEditor {
return <elsa-activity-properties-editor
activity={this.selectedActivity}
variables={this.workflowDefinitionState.variables}
onActivityUpdated={e => this.onActivityUpdated(e)}
onActivityIdUpdated={e => this.onActivityIdUpdated(e)}/>;
onActivityUpdated={e => this.onActivityUpdated(e)}/>;
}
private getWorkflowDefinitionInternal = async (): Promise<WorkflowDefinition> => {
@ -227,26 +215,17 @@ export class WorkflowDefinitionEditor {
private onZoomToFit = async () => await this.canvas.zoomToFit()
private onActivityUpdated = async (e: CustomEvent<ActivityUpdatedArgs>) => {
const updatedActivity = e.detail.activity;
await this.canvas.updateActivity({activity: updatedActivity, id: updatedActivity.id});
await this.canvas.updateActivity({
id: e.detail.newId,
originalId: e.detail.originalId,
activity: e.detail.activity
});
await this.updateModel();
this.emitActivityChangedDebounced({...e.detail, workflowEditor: this.el});
this.saveChangesDebounced();
}
private onActivityIdUpdated = (e: CustomEvent<ActivityIdUpdatedArgs>) => {
// const originalId = e.detail.originalId;
// const newId = e.detail.newId;
// const workflowPath = this.currentWorkflowPath;
// const item = workflowPath.find(x => x.activityId == originalId);
//
// if (!item)
// return;
//
// item.activityId = newId;
// this.createNodeMap(this.workflowDefinition);
}
private onWorkflowPropsUpdated = (e: CustomEvent<WorkflowDefinitionPropsUpdatedArgs>) => {
this.updateModelDebounced();
this.saveChangesDebounced();

View file

@ -1,4 +1,4 @@
import {AddActivityArgs, UpdateActivityArgs} from '../components/designer/canvas/canvas';
import {AddActivityArgs, RenameActivityArgs, UpdateActivityArgs} from '../components/designer/canvas/canvas';
import {Activity} from "../models";
export interface ContainerActivityComponent {
@ -6,6 +6,7 @@ export interface ContainerActivityComponent {
updateLayout(): Promise<void>;
addActivity(args: AddActivityArgs): Promise<Activity>;
updateActivity(args: UpdateActivityArgs): Promise<void>;
renameActivity(args: RenameActivityArgs): Promise<void>;
export(): Promise<Activity>
import(root: Activity): Promise<void>;
zoomToFit(): Promise<void>;