diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index dd6f06981..bb0167777 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -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; "interactiveMode": boolean; "newRoot": () => Promise; + "renameActivity": (args: RenameActivityArgs) => Promise; "reset": () => Promise; "updateActivity": (args: UpdateActivityArgs) => Promise; "updateLayout": () => Promise; @@ -118,6 +119,7 @@ export namespace Components { "import": (root: Activity) => Promise; "interactiveMode": boolean; "newRoot": () => Promise; + "renameActivity": (args: RenameActivityArgs) => Promise; "reset": () => Promise; "updateActivity": (args: UpdateActivityArgs) => Promise; "updateLayout": () => Promise; @@ -717,7 +719,6 @@ declare namespace LocalJSX { } interface ElsaActivityPropertiesEditor { "activity"?: Activity; - "onActivityIdUpdated"?: (event: CustomEvent) => void; "onActivityUpdated"?: (event: CustomEvent) => void; "onDeleteActivityRequested"?: (event: CustomEvent) => void; "variables"?: Array; diff --git a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx index 36c424e84..732bbabb2 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx @@ -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 { + await this.root.renameActivity(args); + } + @Method() public async updateLayout(): Promise { await this.root.updateLayout(); diff --git a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx index 6e927e2a9..714fcee12 100644 --- a/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/activity-definitions/components/editor.tsx @@ -84,11 +84,6 @@ export class Editor { const parentActivityId = parentActivity.id; } - @Listen('activityDeleted') - private async handleActivityDeleted(e: CustomEvent) { - //this.selectedActivity = this.getCurrentContainer(); - } - @Listen('graphUpdated') private async handleGraphUpdated(e: CustomEvent) { this.updateModelDebounced(); @@ -160,8 +155,7 @@ export class Editor { return this.onActivityUpdated(e)} - onActivityIdUpdated={e => this.onActivityIdUpdated(e)}/>; + onActivityUpdated={e => this.onActivityUpdated(e)}/>; } private getActivityDefinitionInternal = async (): Promise => { @@ -220,26 +214,17 @@ export class Editor { private onZoomToFit = async () => await this.canvas.zoomToFit() private onActivityUpdated = async (e: CustomEvent) => { - 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) => { - // 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) => { this.updateModelDebounced(); this.saveChangesDebounced(); diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx index 66e67f28c..bd1ed1686 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx @@ -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; diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx index 913a97dda..e9c144778 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/activity-properties-editor.tsx @@ -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 = []; @Event() activityUpdated: EventEmitter; - @Event() activityIdUpdated: EventEmitter; @Event() deleteActivityRequested: EventEmitter; @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 = 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; diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx index 8d04afb37..0c0d91cac 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx @@ -79,14 +79,8 @@ export class WorkflowDefinitionEditor { @Listen('childActivitySelected') private async handleChildActivitySelected(e: CustomEvent) { - const {parentActivity, childActivity, port} = e.detail; + const {childActivity} = e.detail; this.selectedActivity = childActivity; - const parentActivityId = parentActivity.id; - } - - @Listen('activityDeleted') - private async handleActivityDeleted(e: CustomEvent) { - //this.selectedActivity = this.getCurrentContainer(); } @Listen('graphUpdated') @@ -122,11 +116,6 @@ export class WorkflowDefinitionEditor { @Method() async updateWorkflowDefinition(workflowDefinition: WorkflowDefinition): Promise { 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 this.onActivityUpdated(e)} - onActivityIdUpdated={e => this.onActivityIdUpdated(e)}/>; + onActivityUpdated={e => this.onActivityUpdated(e)}/>; } private getWorkflowDefinitionInternal = async (): Promise => { @@ -227,26 +215,17 @@ export class WorkflowDefinitionEditor { private onZoomToFit = async () => await this.canvas.zoomToFit() private onActivityUpdated = async (e: CustomEvent) => { - 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) => { - // 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) => { this.updateModelDebounced(); this.saveChangesDebounced(); diff --git a/src/designer/elsa-workflows-designer/src/services/container-activity-component.tsx b/src/designer/elsa-workflows-designer/src/services/container-activity-component.tsx index cfc4c3cc3..75e3b74a0 100644 --- a/src/designer/elsa-workflows-designer/src/services/container-activity-component.tsx +++ b/src/designer/elsa-workflows-designer/src/services/container-activity-component.tsx @@ -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; addActivity(args: AddActivityArgs): Promise; updateActivity(args: UpdateActivityArgs): Promise; + renameActivity(args: RenameActivityArgs): Promise; export(): Promise import(root: Activity): Promise; zoomToFit(): Promise;