verben-workflow-ui 0.5.72 → 0.5.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -365,6 +365,43 @@ class WorkflowDesignerState {
365
365
  const sourceNodeType = sourceNodeInfo.node.type;
366
366
  return this.connectionRules[sourceNodeType] || [];
367
367
  }
368
+ /**
369
+ * Deletes a swimlane. Only succeeds when the lane has no nodes — stages must
370
+ * be moved to another lane first. Returns false when the lane still has nodes
371
+ * or the index is out of range.
372
+ *
373
+ * Keeps everything aligned after removal:
374
+ * - Re-sequences `order` on the remaining lanes (immutable reassignment so
375
+ * Angular's @for blocks re-render).
376
+ * - Decrements the swimlane indices stored on connections whose endpoints
377
+ * live below the removed lane, since those lanes shift up by one.
378
+ *
379
+ * The lane's original record is left in `swimlaneRecord`, so a previously
380
+ * loaded lane is emitted with `ObjectState.Removed` by
381
+ * `transformToWorkflowModel`; a never-saved lane simply disappears.
382
+ */
383
+ deleteSwimlane(index) {
384
+ if (index < 0 || index >= this.swimlanes.length) {
385
+ return false;
386
+ }
387
+ if (this.swimlanes[index].nodes?.length) {
388
+ console.warn('Cannot delete a swimlane that still has stages; move them out first.');
389
+ return false;
390
+ }
391
+ this.swimlanes = this.swimlanes
392
+ .filter((_, i) => i !== index)
393
+ .map((lane, i) => ({ ...lane, order: i }));
394
+ this.connections = this.connections.map((conn) => ({
395
+ ...conn,
396
+ sourceSwimlaneIndex: conn.sourceSwimlaneIndex > index
397
+ ? conn.sourceSwimlaneIndex - 1
398
+ : conn.sourceSwimlaneIndex,
399
+ targetSwimlaneIndex: conn.targetSwimlaneIndex > index
400
+ ? conn.targetSwimlaneIndex - 1
401
+ : conn.targetSwimlaneIndex,
402
+ }));
403
+ return true;
404
+ }
368
405
  updateSwimlane(index, name, tags) {
369
406
  console.log('State: Updating swimlane at index', index, 'with name', name, 'and tags', tags);
370
407
  if (index >= 0 && index < this.swimlanes.length) {
@@ -770,6 +807,10 @@ class WorkflowDataService {
770
807
  const url = `DeleteWorkflowAction`;
771
808
  return this.httpService.post(url, requests, this.envSvc.environment.WorkFlowAPI);
772
809
  }
810
+ deleteSwimLanes(requests) {
811
+ const url = `DeleteSwimLane`;
812
+ return this.httpService.post(url, requests, this.envSvc.environment.WorkFlowAPI);
813
+ }
773
814
  saveEscalations(requests) {
774
815
  const url = `SaveEscalations`;
775
816
  // console.log('Saving workflows', requests);
@@ -2531,7 +2572,14 @@ class DesignerCanvasComponent {
2531
2572
  const nodeInfo = this.state.findNodeById(stageId);
2532
2573
  if (nodeInfo) {
2533
2574
  this.activeStageId = stageId;
2534
- this.activeStageData = { ...nodeInfo.node.stageData, id: stageId };
2575
+ // Seed the entry-point toggle from the node's actual start state, which is
2576
+ // the source of truth for the start circle and API serialization. The
2577
+ // auto-assigned entry node has isStartNode=true without stageData.IsEntryPoint.
2578
+ this.activeStageData = {
2579
+ ...nodeInfo.node.stageData,
2580
+ IsEntryPoint: nodeInfo.node.isStartNode,
2581
+ id: stageId,
2582
+ };
2535
2583
  console.log(this.activeStageData, "ACTIVE STAGE DATA");
2536
2584
  this.showStageDialogLocal = true;
2537
2585
  }
@@ -2599,6 +2647,23 @@ class DesignerCanvasComponent {
2599
2647
  ...stageData,
2600
2648
  };
2601
2649
  console.log('Updated stage data:', nodeInfo.node.stageData);
2650
+ // Sync the entry-point flag onto node.isStartNode, which drives both the
2651
+ // start circle and the IsEntryPoint value serialized to the API.
2652
+ if (stageData.IsEntryPoint) {
2653
+ // Enforce a single entry point: this node becomes the sole start node.
2654
+ this.state.swimlanes.forEach((swimlane) => {
2655
+ swimlane.nodes?.forEach((node) => {
2656
+ node.isStartNode = node.id === this.activeStageId;
2657
+ });
2658
+ });
2659
+ this.state.startNodeId = this.activeStageId;
2660
+ }
2661
+ else {
2662
+ nodeInfo.node.isStartNode = false;
2663
+ if (this.state.startNodeId === this.activeStageId) {
2664
+ this.state.startNodeId = null;
2665
+ }
2666
+ }
2602
2667
  // Emit event for parent component to know about the update
2603
2668
  this.stagePropertiesUpdated.emit({
2604
2669
  nodeId: this.activeStageId,
@@ -3622,9 +3687,11 @@ class SwimlaneDialogComponent {
3622
3687
  dataService;
3623
3688
  swimlaneService;
3624
3689
  visible = false;
3690
+ isEditing = false;
3625
3691
  swimlaneData = input(null);
3626
3692
  closed = new EventEmitter();
3627
3693
  created = new EventEmitter();
3694
+ deleted = new EventEmitter();
3628
3695
  searchQuery = '';
3629
3696
  workflowName = '';
3630
3697
  // Track selected tags in a separate array
@@ -3692,6 +3759,9 @@ class SwimlaneDialogComponent {
3692
3759
  this.selectedTagNames.push(tagName);
3693
3760
  }
3694
3761
  }
3762
+ onDeleteSwimlane() {
3763
+ this.deleted.emit();
3764
+ }
3695
3765
  onDialogClose(eventData) {
3696
3766
  console.log('Dialog closed, received data:', eventData);
3697
3767
  this.searchQuery = '';
@@ -3719,17 +3789,21 @@ class SwimlaneDialogComponent {
3719
3789
  this.searchQuery = '';
3720
3790
  }
3721
3791
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SwimlaneDialogComponent, deps: [{ token: i1$1.FormBuilder }, { token: WorkflowDataService }, { token: SwimlaneService }], target: i0.ɵɵFactoryTarget.Component });
3722
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.13", type: SwimlaneDialogComponent, selector: "lib-swimlane-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: false, isRequired: false, transformFunction: null }, swimlaneData: { classPropertyName: "swimlaneData", publicName: "swimlaneData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", created: "created" }, ngImport: i0, template: "<verben-dialogue\n [showCloseIcon]=\"true\"\n [dismissOutsideClick]=\"true\"\n [closeOnEscape]=\"true\"\n [size]=\"'medium'\"\n [mode]=\"'drawer'\"\n [disableFooter]=\"false\"\n [isVisible]=\"visible\"\n [headerTemplate]=\"headerTemplate\"\n [bodyTemplate]=\"bodyTemplate\"\n [footerTemplate]=\"footerTemplate\"\n (openModal)=\"onDialogOpen($event)\"\n (closeModal)=\"onDialogClose($event)\"\n>\n</verben-dialogue>\n\n<ng-template #headerTemplate>\n <div\n class=\"flex items-center justify-between p-3 text-xs border-b-4 border-[#FFE681]\"\n >\n <button class=\"mr-4\" type=\"button\" (click)=\"onDialogClose($event)\">\n <span\n class=\"block w-2.5 h-2.5 border-t border-l border-[#3E3E3E]/70 transform -rotate-45\"\n ></span>\n </button>\n <div class=\"text-center\">\n <h2 class=\"text-sm font-medium\">Select Tags</h2>\n <span class=\"text-xs text-[#3E3E3E]/70\"\n >{{ this.selectedTags.length }}/{{ tags.length }}</span\n >\n </div>\n <span\n class=\"font-medium cursor-pointer text-[#3E3E3E]/70\"\n (click)=\"applySelection()\"\n >Create</span\n >\n </div>\n</ng-template>\n\n<ng-template #bodyTemplate>\n <div class=\"p-3\">\n <div class=\"mb-4 grid gap-4\">\n <!-- <input\n type=\"text\"\n placeholder=\"Enter Name\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"workflowName\"\n />\n\n <input\n type=\"search\"\n placeholder=\"Filter Tags\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"searchQuery\"\n />\n </div>\n\n <div class=\"\">\n <div class=\"py-3\">\n <label class=\"flex items-center\">\n <input type=\"checkbox\" class=\"mr-3\" [(ngModel)]=\"allSelected\" />\n <span class=\"text-sm\">Select All</span>\n </label>\n </div>\n\n <div *ngFor=\"let tag of filteredTags\" class=\"py-3\">\n <label class=\"flex items-center\">\n <input\n type=\"checkbox\"\n class=\"mr-3\"\n [checked]=\"isTagSelected(tag.Name)\"\n (change)=\"toggleTagSelection(tag.Name)\"\n />\n <span class=\"text-sm\">{{ tag.Name }}</span>\n <span *ngIf=\"!tag.IsOptional\" class=\"ml-2 text-xs text-red-500\"\n >(Required)</span\n >\n </label>\n </div> -->\n\n <verbena-input\n [(ngModel)]=\"workflowName\"\n [border]=\"'1px solid #9A9FBF66'\"\n placeHolder=\"Enter Swimlane Label\"\n [bgColor]=\"'white'\"\n ></verbena-input>\n\n <verben-drop-down\n selectKey=\"Code\"\n label=\"Tags\"\n styleClass=\"w-full\"\n width=\"100%\"\n [multiselect]=\"true\"\n placeholder=\"Select\"\n [options]=\"tags\"\n optionLabel=\"Name\"\n id=\"tags\"\n [(ngModel)]=\"selectedTags\"\n class=\"form-control\"\n [filter]=\"true\"\n [filterBy]=\"'Name'\"\n [showClear]=\"true\"\n >\n </verben-drop-down>\n </div>\n </div>\n</ng-template>\n\n<ng-template #footerTemplate>\n <div\n class=\"flex justify-between w-full items-center p-4 border-t border-gray-200\"\n >\n <!-- <button\n class=\"px-4 py-2 mr-2 bg-white border border-gray-200 rounded text-sm font-medium\"\n (click)=\"onDialogClose($event)\"\n >\n Cancel\n </button> -->\n\n <verbena-button\n (click)=\"onDialogClose($event)\"\n text=\"Cancel\"\n styleType=\"ylw-outline\"\n ></verbena-button>\n\n <verbena-button\n type=\"submit\"\n text=\"Save\"\n bgColor=\"#FFE681\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n width=\"114px\"\n height=\"39px\"\n [disable]=\"selectedTags.length < 1\"\n (click)=\"applySelection()\"\n ></verbena-button>\n\n <!-- <verbena-button\n width=\"114px\"\n height=\"39px\"\n text=\"Delete\"\n bgColor=\"#999999\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n ></verbena-button> -->\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "component", type: i5.VerbenDialogueComponent, selector: "verben-dialogue", inputs: ["dialogueWidth", "headerTemplate", "bodyTemplate", "footerTemplate", "showCloseIcon", "dismissOutsideClick", "closeOnEscape", "isVisible", "size", "backdropColor", "customClass", "disableFooter", "margin", "padding", "borderRadius", "dialogueBgColor", "width", "closeIconClass", "boxShadow", "enableTransition", "modalData", "mode", "position", "drawerWidth"], outputs: ["openModal", "closeModal"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.VerbenaButtonComponent, selector: "verbena-button", inputs: ["text", "icon", "useIcon", "svgPosition", "iconPosition", "bgColor", "textColor", "border", "borderRadius", "pd", "width", "height", "fontSize", "fontWeight", "disable", "svgSize", "weight", "variant", "styleType", "svg", "svgWidth", "svgHeight", "iconColor", "svgColor", "buttonClass", "buttonTextClass", "isLoading", "spinnerSize", "spinnerColor"] }, { kind: "component", type: i5.DropDownComponent, selector: "verben-drop-down", inputs: ["options", "width", "showHorizontalLine", "horizontalLineColor", "optionLabel", "optionSubLabel", "optionValue", "placeholder", "invalidMessage", "errorPosition", "loadMoreCaption", "display", "showClear", "lazyLoad", "selectKey", "styleClass", "group", "multiselect", "filter", "avoidDuplication", "filterBy", "debounceTime", "minChar", "disabled", "required", "load", "asyncLabel", "search"], outputs: ["optionsChange", "onChange", "onClick", "onClear"] }, { kind: "component", type: i5.VerbenaInputComponent, selector: "verbena-input", inputs: ["label", "placeHolder", "required", "svgPosition", "minLength", "maxLength", "type", "bgColor", "border", "borderRadius", "textColor", "value", "labelPosition", "labelColor", "disable", "readOnly", "min", "max", "showBorder", "showErrorMessage", "errorMessageColor", "errorBorderColor", "errorPosition", "svg", "fontSize", "svgWidth", "svgHeight", "svgColor", "capitalization", "inputContainerClass", "inputFieldClass", "passLength", "inputWrapperClass", "passwordToggle", "customErrorMessages", "icon", "textPass"], outputs: ["valueChange"] }] });
3792
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.13", type: SwimlaneDialogComponent, selector: "lib-swimlane-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: false, isRequired: false, transformFunction: null }, isEditing: { classPropertyName: "isEditing", publicName: "isEditing", isSignal: false, isRequired: false, transformFunction: null }, swimlaneData: { classPropertyName: "swimlaneData", publicName: "swimlaneData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", created: "created", deleted: "deleted" }, ngImport: i0, template: "<verben-dialogue\n [showCloseIcon]=\"true\"\n [dismissOutsideClick]=\"true\"\n [closeOnEscape]=\"true\"\n [size]=\"'medium'\"\n [mode]=\"'drawer'\"\n [disableFooter]=\"false\"\n [isVisible]=\"visible\"\n [headerTemplate]=\"headerTemplate\"\n [bodyTemplate]=\"bodyTemplate\"\n [footerTemplate]=\"footerTemplate\"\n (openModal)=\"onDialogOpen($event)\"\n (closeModal)=\"onDialogClose($event)\"\n>\n</verben-dialogue>\n\n<ng-template #headerTemplate>\n <div\n class=\"flex items-center justify-between p-3 text-xs border-b-4 border-[#FFE681]\"\n >\n <button class=\"mr-4\" type=\"button\" (click)=\"onDialogClose($event)\">\n <span\n class=\"block w-2.5 h-2.5 border-t border-l border-[#3E3E3E]/70 transform -rotate-45\"\n ></span>\n </button>\n <div class=\"text-center\">\n <h2 class=\"text-sm font-medium\">Select Tags</h2>\n <span class=\"text-xs text-[#3E3E3E]/70\"\n >{{ this.selectedTags.length }}/{{ tags.length }}</span\n >\n </div>\n <span\n class=\"font-medium cursor-pointer text-[#3E3E3E]/70\"\n (click)=\"applySelection()\"\n >Create</span\n >\n </div>\n</ng-template>\n\n<ng-template #bodyTemplate>\n <div class=\"p-3\">\n <div class=\"mb-4 grid gap-4\">\n <!-- <input\n type=\"text\"\n placeholder=\"Enter Name\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"workflowName\"\n />\n\n <input\n type=\"search\"\n placeholder=\"Filter Tags\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"searchQuery\"\n />\n </div>\n\n <div class=\"\">\n <div class=\"py-3\">\n <label class=\"flex items-center\">\n <input type=\"checkbox\" class=\"mr-3\" [(ngModel)]=\"allSelected\" />\n <span class=\"text-sm\">Select All</span>\n </label>\n </div>\n\n <div *ngFor=\"let tag of filteredTags\" class=\"py-3\">\n <label class=\"flex items-center\">\n <input\n type=\"checkbox\"\n class=\"mr-3\"\n [checked]=\"isTagSelected(tag.Name)\"\n (change)=\"toggleTagSelection(tag.Name)\"\n />\n <span class=\"text-sm\">{{ tag.Name }}</span>\n <span *ngIf=\"!tag.IsOptional\" class=\"ml-2 text-xs text-red-500\"\n >(Required)</span\n >\n </label>\n </div> -->\n\n <verbena-input\n [(ngModel)]=\"workflowName\"\n [border]=\"'1px solid #9A9FBF66'\"\n placeHolder=\"Enter Swimlane Label\"\n [bgColor]=\"'white'\"\n ></verbena-input>\n\n <verben-drop-down\n selectKey=\"Code\"\n label=\"Tags\"\n styleClass=\"w-full\"\n width=\"100%\"\n [multiselect]=\"true\"\n placeholder=\"Select\"\n [options]=\"tags\"\n optionLabel=\"Name\"\n id=\"tags\"\n [(ngModel)]=\"selectedTags\"\n class=\"form-control\"\n [filter]=\"true\"\n [filterBy]=\"'Name'\"\n [showClear]=\"true\"\n >\n </verben-drop-down>\n </div>\n </div>\n</ng-template>\n\n<ng-template #footerTemplate>\n <div\n class=\"flex justify-between w-full items-center p-4 border-t border-gray-200\"\n >\n <!-- <button\n class=\"px-4 py-2 mr-2 bg-white border border-gray-200 rounded text-sm font-medium\"\n (click)=\"onDialogClose($event)\"\n >\n Cancel\n </button> -->\n\n <verbena-button\n (click)=\"onDialogClose($event)\"\n text=\"Cancel\"\n styleType=\"ylw-outline\"\n ></verbena-button>\n\n <verbena-button\n type=\"submit\"\n text=\"Save\"\n bgColor=\"#FFE681\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n width=\"114px\"\n height=\"39px\"\n [disable]=\"selectedTags.length < 1\"\n (click)=\"applySelection()\"\n ></verbena-button>\n\n <verbena-button\n *ngIf=\"isEditing\"\n width=\"114px\"\n height=\"39px\"\n text=\"Delete\"\n bgColor=\"#999999\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n (click)=\"onDeleteSwimlane()\"\n ></verbena-button>\n </div>\n</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i5.VerbenDialogueComponent, selector: "verben-dialogue", inputs: ["dialogueWidth", "headerTemplate", "bodyTemplate", "footerTemplate", "showCloseIcon", "dismissOutsideClick", "closeOnEscape", "isVisible", "size", "backdropColor", "customClass", "disableFooter", "margin", "padding", "borderRadius", "dialogueBgColor", "width", "closeIconClass", "boxShadow", "enableTransition", "modalData", "mode", "position", "drawerWidth"], outputs: ["openModal", "closeModal"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.VerbenaButtonComponent, selector: "verbena-button", inputs: ["text", "icon", "useIcon", "svgPosition", "iconPosition", "bgColor", "textColor", "border", "borderRadius", "pd", "width", "height", "fontSize", "fontWeight", "disable", "svgSize", "weight", "variant", "styleType", "svg", "svgWidth", "svgHeight", "iconColor", "svgColor", "buttonClass", "buttonTextClass", "isLoading", "spinnerSize", "spinnerColor"] }, { kind: "component", type: i5.DropDownComponent, selector: "verben-drop-down", inputs: ["options", "width", "showHorizontalLine", "horizontalLineColor", "optionLabel", "optionSubLabel", "optionValue", "placeholder", "invalidMessage", "errorPosition", "loadMoreCaption", "display", "showClear", "lazyLoad", "selectKey", "styleClass", "group", "multiselect", "filter", "avoidDuplication", "filterBy", "debounceTime", "minChar", "disabled", "required", "load", "asyncLabel", "search"], outputs: ["optionsChange", "onChange", "onClick", "onClear"] }, { kind: "component", type: i5.VerbenaInputComponent, selector: "verbena-input", inputs: ["label", "placeHolder", "required", "svgPosition", "minLength", "maxLength", "type", "bgColor", "border", "borderRadius", "textColor", "value", "labelPosition", "labelColor", "disable", "readOnly", "min", "max", "showBorder", "showErrorMessage", "errorMessageColor", "errorBorderColor", "errorPosition", "svg", "fontSize", "svgWidth", "svgHeight", "svgColor", "capitalization", "inputContainerClass", "inputFieldClass", "passLength", "inputWrapperClass", "passwordToggle", "customErrorMessages", "icon", "textPass"], outputs: ["valueChange"] }] });
3723
3793
  }
3724
3794
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: SwimlaneDialogComponent, decorators: [{
3725
3795
  type: Component,
3726
- args: [{ selector: 'lib-swimlane-dialog', template: "<verben-dialogue\n [showCloseIcon]=\"true\"\n [dismissOutsideClick]=\"true\"\n [closeOnEscape]=\"true\"\n [size]=\"'medium'\"\n [mode]=\"'drawer'\"\n [disableFooter]=\"false\"\n [isVisible]=\"visible\"\n [headerTemplate]=\"headerTemplate\"\n [bodyTemplate]=\"bodyTemplate\"\n [footerTemplate]=\"footerTemplate\"\n (openModal)=\"onDialogOpen($event)\"\n (closeModal)=\"onDialogClose($event)\"\n>\n</verben-dialogue>\n\n<ng-template #headerTemplate>\n <div\n class=\"flex items-center justify-between p-3 text-xs border-b-4 border-[#FFE681]\"\n >\n <button class=\"mr-4\" type=\"button\" (click)=\"onDialogClose($event)\">\n <span\n class=\"block w-2.5 h-2.5 border-t border-l border-[#3E3E3E]/70 transform -rotate-45\"\n ></span>\n </button>\n <div class=\"text-center\">\n <h2 class=\"text-sm font-medium\">Select Tags</h2>\n <span class=\"text-xs text-[#3E3E3E]/70\"\n >{{ this.selectedTags.length }}/{{ tags.length }}</span\n >\n </div>\n <span\n class=\"font-medium cursor-pointer text-[#3E3E3E]/70\"\n (click)=\"applySelection()\"\n >Create</span\n >\n </div>\n</ng-template>\n\n<ng-template #bodyTemplate>\n <div class=\"p-3\">\n <div class=\"mb-4 grid gap-4\">\n <!-- <input\n type=\"text\"\n placeholder=\"Enter Name\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"workflowName\"\n />\n\n <input\n type=\"search\"\n placeholder=\"Filter Tags\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"searchQuery\"\n />\n </div>\n\n <div class=\"\">\n <div class=\"py-3\">\n <label class=\"flex items-center\">\n <input type=\"checkbox\" class=\"mr-3\" [(ngModel)]=\"allSelected\" />\n <span class=\"text-sm\">Select All</span>\n </label>\n </div>\n\n <div *ngFor=\"let tag of filteredTags\" class=\"py-3\">\n <label class=\"flex items-center\">\n <input\n type=\"checkbox\"\n class=\"mr-3\"\n [checked]=\"isTagSelected(tag.Name)\"\n (change)=\"toggleTagSelection(tag.Name)\"\n />\n <span class=\"text-sm\">{{ tag.Name }}</span>\n <span *ngIf=\"!tag.IsOptional\" class=\"ml-2 text-xs text-red-500\"\n >(Required)</span\n >\n </label>\n </div> -->\n\n <verbena-input\n [(ngModel)]=\"workflowName\"\n [border]=\"'1px solid #9A9FBF66'\"\n placeHolder=\"Enter Swimlane Label\"\n [bgColor]=\"'white'\"\n ></verbena-input>\n\n <verben-drop-down\n selectKey=\"Code\"\n label=\"Tags\"\n styleClass=\"w-full\"\n width=\"100%\"\n [multiselect]=\"true\"\n placeholder=\"Select\"\n [options]=\"tags\"\n optionLabel=\"Name\"\n id=\"tags\"\n [(ngModel)]=\"selectedTags\"\n class=\"form-control\"\n [filter]=\"true\"\n [filterBy]=\"'Name'\"\n [showClear]=\"true\"\n >\n </verben-drop-down>\n </div>\n </div>\n</ng-template>\n\n<ng-template #footerTemplate>\n <div\n class=\"flex justify-between w-full items-center p-4 border-t border-gray-200\"\n >\n <!-- <button\n class=\"px-4 py-2 mr-2 bg-white border border-gray-200 rounded text-sm font-medium\"\n (click)=\"onDialogClose($event)\"\n >\n Cancel\n </button> -->\n\n <verbena-button\n (click)=\"onDialogClose($event)\"\n text=\"Cancel\"\n styleType=\"ylw-outline\"\n ></verbena-button>\n\n <verbena-button\n type=\"submit\"\n text=\"Save\"\n bgColor=\"#FFE681\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n width=\"114px\"\n height=\"39px\"\n [disable]=\"selectedTags.length < 1\"\n (click)=\"applySelection()\"\n ></verbena-button>\n\n <!-- <verbena-button\n width=\"114px\"\n height=\"39px\"\n text=\"Delete\"\n bgColor=\"#999999\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n ></verbena-button> -->\n </div>\n</ng-template>\n" }]
3796
+ args: [{ selector: 'lib-swimlane-dialog', template: "<verben-dialogue\n [showCloseIcon]=\"true\"\n [dismissOutsideClick]=\"true\"\n [closeOnEscape]=\"true\"\n [size]=\"'medium'\"\n [mode]=\"'drawer'\"\n [disableFooter]=\"false\"\n [isVisible]=\"visible\"\n [headerTemplate]=\"headerTemplate\"\n [bodyTemplate]=\"bodyTemplate\"\n [footerTemplate]=\"footerTemplate\"\n (openModal)=\"onDialogOpen($event)\"\n (closeModal)=\"onDialogClose($event)\"\n>\n</verben-dialogue>\n\n<ng-template #headerTemplate>\n <div\n class=\"flex items-center justify-between p-3 text-xs border-b-4 border-[#FFE681]\"\n >\n <button class=\"mr-4\" type=\"button\" (click)=\"onDialogClose($event)\">\n <span\n class=\"block w-2.5 h-2.5 border-t border-l border-[#3E3E3E]/70 transform -rotate-45\"\n ></span>\n </button>\n <div class=\"text-center\">\n <h2 class=\"text-sm font-medium\">Select Tags</h2>\n <span class=\"text-xs text-[#3E3E3E]/70\"\n >{{ this.selectedTags.length }}/{{ tags.length }}</span\n >\n </div>\n <span\n class=\"font-medium cursor-pointer text-[#3E3E3E]/70\"\n (click)=\"applySelection()\"\n >Create</span\n >\n </div>\n</ng-template>\n\n<ng-template #bodyTemplate>\n <div class=\"p-3\">\n <div class=\"mb-4 grid gap-4\">\n <!-- <input\n type=\"text\"\n placeholder=\"Enter Name\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"workflowName\"\n />\n\n <input\n type=\"search\"\n placeholder=\"Filter Tags\"\n class=\"w-full px-4 py-2 rounded-lg border border-[#E7C10B] outline-none\"\n [(ngModel)]=\"searchQuery\"\n />\n </div>\n\n <div class=\"\">\n <div class=\"py-3\">\n <label class=\"flex items-center\">\n <input type=\"checkbox\" class=\"mr-3\" [(ngModel)]=\"allSelected\" />\n <span class=\"text-sm\">Select All</span>\n </label>\n </div>\n\n <div *ngFor=\"let tag of filteredTags\" class=\"py-3\">\n <label class=\"flex items-center\">\n <input\n type=\"checkbox\"\n class=\"mr-3\"\n [checked]=\"isTagSelected(tag.Name)\"\n (change)=\"toggleTagSelection(tag.Name)\"\n />\n <span class=\"text-sm\">{{ tag.Name }}</span>\n <span *ngIf=\"!tag.IsOptional\" class=\"ml-2 text-xs text-red-500\"\n >(Required)</span\n >\n </label>\n </div> -->\n\n <verbena-input\n [(ngModel)]=\"workflowName\"\n [border]=\"'1px solid #9A9FBF66'\"\n placeHolder=\"Enter Swimlane Label\"\n [bgColor]=\"'white'\"\n ></verbena-input>\n\n <verben-drop-down\n selectKey=\"Code\"\n label=\"Tags\"\n styleClass=\"w-full\"\n width=\"100%\"\n [multiselect]=\"true\"\n placeholder=\"Select\"\n [options]=\"tags\"\n optionLabel=\"Name\"\n id=\"tags\"\n [(ngModel)]=\"selectedTags\"\n class=\"form-control\"\n [filter]=\"true\"\n [filterBy]=\"'Name'\"\n [showClear]=\"true\"\n >\n </verben-drop-down>\n </div>\n </div>\n</ng-template>\n\n<ng-template #footerTemplate>\n <div\n class=\"flex justify-between w-full items-center p-4 border-t border-gray-200\"\n >\n <!-- <button\n class=\"px-4 py-2 mr-2 bg-white border border-gray-200 rounded text-sm font-medium\"\n (click)=\"onDialogClose($event)\"\n >\n Cancel\n </button> -->\n\n <verbena-button\n (click)=\"onDialogClose($event)\"\n text=\"Cancel\"\n styleType=\"ylw-outline\"\n ></verbena-button>\n\n <verbena-button\n type=\"submit\"\n text=\"Save\"\n bgColor=\"#FFE681\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n width=\"114px\"\n height=\"39px\"\n [disable]=\"selectedTags.length < 1\"\n (click)=\"applySelection()\"\n ></verbena-button>\n\n <verbena-button\n *ngIf=\"isEditing\"\n width=\"114px\"\n height=\"39px\"\n text=\"Delete\"\n bgColor=\"#999999\"\n textColor=\"#404040\"\n borderRadius=\"10px\"\n pd=\"10px 20px\"\n (click)=\"onDeleteSwimlane()\"\n ></verbena-button>\n </div>\n</ng-template>\n" }]
3727
3797
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: WorkflowDataService }, { type: SwimlaneService }], propDecorators: { visible: [{
3728
3798
  type: Input
3799
+ }], isEditing: [{
3800
+ type: Input
3729
3801
  }], closed: [{
3730
3802
  type: Output
3731
3803
  }], created: [{
3732
3804
  type: Output
3805
+ }], deleted: [{
3806
+ type: Output
3733
3807
  }] } });
3734
3808
 
3735
3809
  class ActorTagsDialogComponent {
@@ -4131,6 +4205,48 @@ class WorkflowDesignerComponent {
4131
4205
  this.showSwimlaneDialog = false;
4132
4206
  this.editingSwimlaneIndex = null;
4133
4207
  }
4208
+ onSwimlaneDialogDeleted() {
4209
+ if (this.editingSwimlaneIndex === null) {
4210
+ return;
4211
+ }
4212
+ const index = this.editingSwimlaneIndex;
4213
+ const swimlane = this.state.swimlanes[index];
4214
+ // Guard: stages must be moved out first (also enforced in state.deleteSwimlane).
4215
+ if (swimlane?.nodes?.length) {
4216
+ this.utilService.showError('Move all stages out of this swimlane before deleting it.');
4217
+ return;
4218
+ }
4219
+ // A loaded lane keeps its backend Code in swimlaneRecord (keyed by id/Code).
4220
+ const backendRecord = swimlane?.id
4221
+ ? this.state.swimlaneRecord[swimlane.id]
4222
+ : undefined;
4223
+ if (backendRecord?.Code) {
4224
+ // Path A: backend-persisted lane — delete on the server first.
4225
+ this.dataService
4226
+ .deleteSwimLanes([backendRecord.Code])
4227
+ .then(() => {
4228
+ this.finishSwimlaneDelete(index);
4229
+ })
4230
+ .catch((error) => {
4231
+ console.error('Error deleting swimlane:', error);
4232
+ this.utilService.showError('Failed to delete swimlane');
4233
+ });
4234
+ }
4235
+ else {
4236
+ // Path B: never-saved lane — remove from state directly, no API call.
4237
+ this.finishSwimlaneDelete(index);
4238
+ }
4239
+ }
4240
+ finishSwimlaneDelete(index) {
4241
+ const deleted = this.state.deleteSwimlane(index);
4242
+ if (!deleted) {
4243
+ this.utilService.showError('Move all stages out of this swimlane before deleting it.');
4244
+ return;
4245
+ }
4246
+ this.showSwimlaneDialog = false;
4247
+ this.editingSwimlaneIndex = null;
4248
+ this.canvasRef?.updateCanvasSize();
4249
+ }
4134
4250
  onSubflowSelected() {
4135
4251
  // Reset the selected tool after placing a subflow node
4136
4252
  this.selectedTool = null;
@@ -4139,11 +4255,11 @@ class WorkflowDesignerComponent {
4139
4255
  this.showActorTagsDialog = true;
4140
4256
  }
4141
4257
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WorkflowDesignerComponent, deps: [{ token: WorkflowDesignerState }, { token: WorkflowDataService }, { token: i1.UtilService }], target: i0.ɵɵFactoryTarget.Component });
4142
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: WorkflowDesignerComponent, selector: "lib-workflow-designer", inputs: { workflowCode: "workflowCode" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvasRef"], descendants: true }], ngImport: i0, template: "<div class=\"workflow-designer\">\n <div class=\"workflow-header\">\n <h1>{{ state.workflow?.Name }}</h1>\n </div>\n\n <!-- Toolbar Component -->\n <lib-designer-toolbar [selectedTool]=\"selectedTool\" [isSaving]=\"isSaving\"\n [workflowCode]=\"state.workflow?.Code || null\" (toolSelected)=\"onToolSelected($event)\"\n (saveWorkflow)=\"saveWorkflow()\" (openActorTags)=\"openActorTagsDialog()\">\n </lib-designer-toolbar>\n\n <!-- Show loading indicator if needed -->\n <div *ngIf=\"isLoading\" class=\"loading-overlay\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">Loading workflow...</div>\n </div>\n\n <!-- Canvas Component -->\n <lib-designer-canvas [selectedTool]=\"selectedTool\" (clickedPosition)=\"handleCanvasPositionClick($event)\"\n (subflowSelected)=\"onSubflowSelected()\" #canvasRef>\n </lib-designer-canvas>\n\n <lib-swimlane-dialog [visible]=\"showSwimlaneDialog\" [swimlaneData]=\"editingSwimlane()\"\n (created)=\"onSwimlaneDialogFilled($event)\" (closed)=\"showSwimlaneDialog = false\"></lib-swimlane-dialog>\n\n <lib-actor-tags-dialog [visible]=\"showActorTagsDialog\" [workflowCode]=\"state.workflow?.Code || null\"\n (closed)=\"showActorTagsDialog = false\"></lib-actor-tags-dialog>\n</div>\n", styles: [".workflow-designer{display:flex;flex-direction:column;height:100vh;background-color:#f8fafc}.workflow-header{padding:1rem;background-color:#fff;border-bottom:1px solid #e2e8f0;text-align:center}.workflow-header h1{font-size:1.25rem;font-weight:600;color:#334155;margin:0}.loading-overlay{position:absolute;inset:0;background-color:#ffffffb3;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:1000}.loading-spinner{border:4px solid #f3f3f3;border-top:4px solid #d8b4fe;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.loading-text{margin-top:1rem;font-size:1rem;color:#7e22ce}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: DesignerToolbarComponent, selector: "lib-designer-toolbar", inputs: ["selectedTool", "isSaving", "workflowCode"], outputs: ["openActorTags", "toolSelected", "saveWorkflow"] }, { kind: "component", type: DesignerCanvasComponent, selector: "lib-designer-canvas", inputs: ["selectedTool"], outputs: ["clickedPosition", "subflowSelected", "stagePropertiesUpdated", "actionPropertiesUpdated"] }, { kind: "component", type: SwimlaneDialogComponent, selector: "lib-swimlane-dialog", inputs: ["visible", "swimlaneData"], outputs: ["closed", "created"] }, { kind: "component", type: ActorTagsDialogComponent, selector: "lib-actor-tags-dialog", inputs: ["visible", "workflowCode"], outputs: ["closed"] }] });
4258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: WorkflowDesignerComponent, selector: "lib-workflow-designer", inputs: { workflowCode: "workflowCode" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvasRef"], descendants: true }], ngImport: i0, template: "<div class=\"workflow-designer\">\n <div class=\"workflow-header\">\n <h1>{{ state.workflow?.Name }}</h1>\n </div>\n\n <!-- Toolbar Component -->\n <lib-designer-toolbar [selectedTool]=\"selectedTool\" [isSaving]=\"isSaving\"\n [workflowCode]=\"state.workflow?.Code || null\" (toolSelected)=\"onToolSelected($event)\"\n (saveWorkflow)=\"saveWorkflow()\" (openActorTags)=\"openActorTagsDialog()\">\n </lib-designer-toolbar>\n\n <!-- Show loading indicator if needed -->\n <div *ngIf=\"isLoading\" class=\"loading-overlay\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">Loading workflow...</div>\n </div>\n\n <!-- Canvas Component -->\n <lib-designer-canvas [selectedTool]=\"selectedTool\" (clickedPosition)=\"handleCanvasPositionClick($event)\"\n (subflowSelected)=\"onSubflowSelected()\" #canvasRef>\n </lib-designer-canvas>\n\n <lib-swimlane-dialog [visible]=\"showSwimlaneDialog\" [swimlaneData]=\"editingSwimlane()\"\n [isEditing]=\"editingSwimlaneIndex !== null\" (created)=\"onSwimlaneDialogFilled($event)\"\n (deleted)=\"onSwimlaneDialogDeleted()\" (closed)=\"showSwimlaneDialog = false\"></lib-swimlane-dialog>\n\n <lib-actor-tags-dialog [visible]=\"showActorTagsDialog\" [workflowCode]=\"state.workflow?.Code || null\"\n (closed)=\"showActorTagsDialog = false\"></lib-actor-tags-dialog>\n</div>\n", styles: [".workflow-designer{display:flex;flex-direction:column;height:100vh;background-color:#f8fafc}.workflow-header{padding:1rem;background-color:#fff;border-bottom:1px solid #e2e8f0;text-align:center}.workflow-header h1{font-size:1.25rem;font-weight:600;color:#334155;margin:0}.loading-overlay{position:absolute;inset:0;background-color:#ffffffb3;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:1000}.loading-spinner{border:4px solid #f3f3f3;border-top:4px solid #d8b4fe;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.loading-text{margin-top:1rem;font-size:1rem;color:#7e22ce}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: DesignerToolbarComponent, selector: "lib-designer-toolbar", inputs: ["selectedTool", "isSaving", "workflowCode"], outputs: ["openActorTags", "toolSelected", "saveWorkflow"] }, { kind: "component", type: DesignerCanvasComponent, selector: "lib-designer-canvas", inputs: ["selectedTool"], outputs: ["clickedPosition", "subflowSelected", "stagePropertiesUpdated", "actionPropertiesUpdated"] }, { kind: "component", type: SwimlaneDialogComponent, selector: "lib-swimlane-dialog", inputs: ["visible", "isEditing", "swimlaneData"], outputs: ["closed", "created", "deleted"] }, { kind: "component", type: ActorTagsDialogComponent, selector: "lib-actor-tags-dialog", inputs: ["visible", "workflowCode"], outputs: ["closed"] }] });
4143
4259
  }
4144
4260
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: WorkflowDesignerComponent, decorators: [{
4145
4261
  type: Component,
4146
- args: [{ selector: 'lib-workflow-designer', template: "<div class=\"workflow-designer\">\n <div class=\"workflow-header\">\n <h1>{{ state.workflow?.Name }}</h1>\n </div>\n\n <!-- Toolbar Component -->\n <lib-designer-toolbar [selectedTool]=\"selectedTool\" [isSaving]=\"isSaving\"\n [workflowCode]=\"state.workflow?.Code || null\" (toolSelected)=\"onToolSelected($event)\"\n (saveWorkflow)=\"saveWorkflow()\" (openActorTags)=\"openActorTagsDialog()\">\n </lib-designer-toolbar>\n\n <!-- Show loading indicator if needed -->\n <div *ngIf=\"isLoading\" class=\"loading-overlay\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">Loading workflow...</div>\n </div>\n\n <!-- Canvas Component -->\n <lib-designer-canvas [selectedTool]=\"selectedTool\" (clickedPosition)=\"handleCanvasPositionClick($event)\"\n (subflowSelected)=\"onSubflowSelected()\" #canvasRef>\n </lib-designer-canvas>\n\n <lib-swimlane-dialog [visible]=\"showSwimlaneDialog\" [swimlaneData]=\"editingSwimlane()\"\n (created)=\"onSwimlaneDialogFilled($event)\" (closed)=\"showSwimlaneDialog = false\"></lib-swimlane-dialog>\n\n <lib-actor-tags-dialog [visible]=\"showActorTagsDialog\" [workflowCode]=\"state.workflow?.Code || null\"\n (closed)=\"showActorTagsDialog = false\"></lib-actor-tags-dialog>\n</div>\n", styles: [".workflow-designer{display:flex;flex-direction:column;height:100vh;background-color:#f8fafc}.workflow-header{padding:1rem;background-color:#fff;border-bottom:1px solid #e2e8f0;text-align:center}.workflow-header h1{font-size:1.25rem;font-weight:600;color:#334155;margin:0}.loading-overlay{position:absolute;inset:0;background-color:#ffffffb3;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:1000}.loading-spinner{border:4px solid #f3f3f3;border-top:4px solid #d8b4fe;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.loading-text{margin-top:1rem;font-size:1rem;color:#7e22ce}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
4262
+ args: [{ selector: 'lib-workflow-designer', template: "<div class=\"workflow-designer\">\n <div class=\"workflow-header\">\n <h1>{{ state.workflow?.Name }}</h1>\n </div>\n\n <!-- Toolbar Component -->\n <lib-designer-toolbar [selectedTool]=\"selectedTool\" [isSaving]=\"isSaving\"\n [workflowCode]=\"state.workflow?.Code || null\" (toolSelected)=\"onToolSelected($event)\"\n (saveWorkflow)=\"saveWorkflow()\" (openActorTags)=\"openActorTagsDialog()\">\n </lib-designer-toolbar>\n\n <!-- Show loading indicator if needed -->\n <div *ngIf=\"isLoading\" class=\"loading-overlay\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\">Loading workflow...</div>\n </div>\n\n <!-- Canvas Component -->\n <lib-designer-canvas [selectedTool]=\"selectedTool\" (clickedPosition)=\"handleCanvasPositionClick($event)\"\n (subflowSelected)=\"onSubflowSelected()\" #canvasRef>\n </lib-designer-canvas>\n\n <lib-swimlane-dialog [visible]=\"showSwimlaneDialog\" [swimlaneData]=\"editingSwimlane()\"\n [isEditing]=\"editingSwimlaneIndex !== null\" (created)=\"onSwimlaneDialogFilled($event)\"\n (deleted)=\"onSwimlaneDialogDeleted()\" (closed)=\"showSwimlaneDialog = false\"></lib-swimlane-dialog>\n\n <lib-actor-tags-dialog [visible]=\"showActorTagsDialog\" [workflowCode]=\"state.workflow?.Code || null\"\n (closed)=\"showActorTagsDialog = false\"></lib-actor-tags-dialog>\n</div>\n", styles: [".workflow-designer{display:flex;flex-direction:column;height:100vh;background-color:#f8fafc}.workflow-header{padding:1rem;background-color:#fff;border-bottom:1px solid #e2e8f0;text-align:center}.workflow-header h1{font-size:1.25rem;font-weight:600;color:#334155;margin:0}.loading-overlay{position:absolute;inset:0;background-color:#ffffffb3;display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:1000}.loading-spinner{border:4px solid #f3f3f3;border-top:4px solid #d8b4fe;border-radius:50%;width:40px;height:40px;animation:spin 1s linear infinite}.loading-text{margin-top:1rem;font-size:1rem;color:#7e22ce}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
4147
4263
  }], ctorParameters: () => [{ type: WorkflowDesignerState }, { type: WorkflowDataService }, { type: i1.UtilService }], propDecorators: { canvasRef: [{
4148
4264
  type: ViewChild,
4149
4265
  args: ['canvasRef']