@brggroup/share-lib 0.0.36 → 0.0.38

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.
@@ -42,6 +42,26 @@ import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
42
42
  import { trigger, transition, animate, style } from '@angular/animations';
43
43
  import * as i2$3 from 'ng-zorro-antd/spin';
44
44
  import { NzSpinModule } from 'ng-zorro-antd/spin';
45
+ import * as i3$1 from 'ng-zorro-antd/card';
46
+ import { NzCardModule } from 'ng-zorro-antd/card';
47
+ import * as i4$1 from 'ng-zorro-antd/drawer';
48
+ import { NzDrawerModule } from 'ng-zorro-antd/drawer';
49
+ import * as i5$2 from 'ng-zorro-antd/tag';
50
+ import { NzTagModule } from 'ng-zorro-antd/tag';
51
+ import * as i6$3 from 'ng-zorro-antd/radio';
52
+ import { NzRadioModule } from 'ng-zorro-antd/radio';
53
+ import * as i8 from 'ng-zorro-antd/button';
54
+ import { NzButtonModule } from 'ng-zorro-antd/button';
55
+ import * as i11 from 'ng-zorro-antd/tooltip';
56
+ import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
57
+ import * as i14 from 'ng-zorro-antd/flex';
58
+ import { NzFlexModule } from 'ng-zorro-antd/flex';
59
+ import * as i15 from 'ng-zorro-antd/table';
60
+ import { NzTableModule } from 'ng-zorro-antd/table';
61
+ import * as i16 from 'ng-zorro-antd/divider';
62
+ import { NzDividerModule } from 'ng-zorro-antd/divider';
63
+ import * as i9 from 'ng-zorro-antd/core/transition-patch';
64
+ import * as i10 from 'ng-zorro-antd/core/wave';
45
65
  import * as XLSX from 'xlsx';
46
66
 
47
67
  var TranslateKey;
@@ -2325,7 +2345,10 @@ class ExtendSelectComponent {
2325
2345
  return item[this.displayField];
2326
2346
  }
2327
2347
  if (this.displayFields?.length) {
2328
- const display = this.displayFields.map((field) => item[field]).join(' - ');
2348
+ const display = this.displayFields
2349
+ .map((field) => item[field])
2350
+ .filter((x) => x)
2351
+ .join(' - ');
2329
2352
  return display;
2330
2353
  }
2331
2354
  return item;
@@ -2680,6 +2703,1604 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.7", ngImpor
2680
2703
  type: Input
2681
2704
  }] } });
2682
2705
 
2706
+ class WF_Template {
2707
+ // #region properties
2708
+ WF_Template_Id;
2709
+ App_Org_Id;
2710
+ CreatedDate;
2711
+ CreatedUser;
2712
+ UpdatedDate;
2713
+ UpdatedUser;
2714
+ IsActive;
2715
+ Code;
2716
+ Name;
2717
+ DocType;
2718
+ Description;
2719
+ TemplatePrint;
2720
+ // #endregion properties
2721
+ constructor(obj) {
2722
+ obj = obj || {};
2723
+ this.WF_Template_Id = obj.WF_Template_Id || null;
2724
+ this.App_Org_Id = obj.App_Org_Id || '';
2725
+ this.CreatedDate = obj.CreatedDate || null;
2726
+ this.CreatedUser = obj.CreatedUser || '';
2727
+ this.UpdatedDate = obj.UpdatedDate || null;
2728
+ this.UpdatedUser = obj.UpdatedUser || '';
2729
+ this.IsActive = obj.IsActive || null;
2730
+ this.Code = obj.Code || '';
2731
+ this.Name = obj.Name || '';
2732
+ this.DocType = obj.DocType || '';
2733
+ this.Description = obj.Description || '';
2734
+ this.TemplatePrint = obj.TemplatePrint || '';
2735
+ }
2736
+ }
2737
+ class WF_TemplateStage {
2738
+ // #region properties
2739
+ WF_TemplateStage_Id;
2740
+ App_Org_Id;
2741
+ CreatedDate;
2742
+ CreatedUser;
2743
+ UpdatedDate;
2744
+ UpdatedUser;
2745
+ IsActive;
2746
+ WF_Template_Id;
2747
+ Code;
2748
+ Name;
2749
+ StageType;
2750
+ SeqValue;
2751
+ LimitValue;
2752
+ IsRequireUser;
2753
+ // #endregion properties
2754
+ constructor(obj) {
2755
+ obj = obj || {};
2756
+ this.WF_TemplateStage_Id = obj.WF_TemplateStage_Id || null;
2757
+ this.App_Org_Id = obj.App_Org_Id || '';
2758
+ this.CreatedDate = obj.CreatedDate || null;
2759
+ this.CreatedUser = obj.CreatedUser || '';
2760
+ this.UpdatedDate = obj.UpdatedDate || null;
2761
+ this.UpdatedUser = obj.UpdatedUser || '';
2762
+ this.IsActive = obj.IsActive || null;
2763
+ this.WF_Template_Id = obj.WF_Template_Id || null;
2764
+ this.Code = obj.Code || '';
2765
+ this.Name = obj.Name || '';
2766
+ this.StageType = obj.StageType || '';
2767
+ this.SeqValue = obj.SeqValue || null;
2768
+ this.LimitValue = obj.LimitValue || null;
2769
+ this.IsRequireUser = obj.IsRequireUser || null;
2770
+ }
2771
+ }
2772
+ class WF_TemplateStageAction {
2773
+ // #region properties
2774
+ WF_TemplateStageAction_Id;
2775
+ App_Org_Id;
2776
+ CreatedDate;
2777
+ CreatedUser;
2778
+ UpdatedDate;
2779
+ UpdatedUser;
2780
+ IsActive;
2781
+ WF_Template_Id;
2782
+ WF_TemplateStage_Id;
2783
+ ActionType;
2784
+ ActionStatus;
2785
+ ActionText;
2786
+ StageStatus;
2787
+ AllowNote;
2788
+ AllowEmail;
2789
+ WF_EmailTemplate;
2790
+ To_WF_TemplateStage_Id;
2791
+ To_StageStatus;
2792
+ WF_TransitionsRule_Id;
2793
+ // #endregion properties
2794
+ constructor(obj) {
2795
+ obj = obj || {};
2796
+ this.WF_TemplateStageAction_Id = obj.WF_TemplateStageAction_Id || null;
2797
+ this.App_Org_Id = obj.App_Org_Id || '';
2798
+ this.CreatedDate = obj.CreatedDate || null;
2799
+ this.CreatedUser = obj.CreatedUser || '';
2800
+ this.UpdatedDate = obj.UpdatedDate || null;
2801
+ this.UpdatedUser = obj.UpdatedUser || '';
2802
+ this.IsActive = obj.IsActive || null;
2803
+ this.WF_Template_Id = obj.WF_Template_Id || null;
2804
+ this.WF_TemplateStage_Id = obj.WF_TemplateStage_Id || null;
2805
+ this.ActionType = obj.ActionType || '';
2806
+ this.ActionStatus = obj.ActionStatus || '';
2807
+ this.ActionText = obj.ActionText || '';
2808
+ this.StageStatus = obj.StageStatus || '';
2809
+ this.AllowNote = obj.AllowNote || null;
2810
+ this.AllowEmail = obj.AllowEmail || null;
2811
+ this.WF_EmailTemplate = obj.WF_EmailTemplate || '';
2812
+ this.To_WF_TemplateStage_Id = obj.To_WF_TemplateStage_Id || null;
2813
+ this.To_StageStatus = obj.To_StageStatus || '';
2814
+ this.WF_TransitionsRule_Id = obj.WF_TransitionsRule_Id || null;
2815
+ }
2816
+ }
2817
+ class Workflow extends WF_Template {
2818
+ lstStageStatus = [];
2819
+ lstActionType = [];
2820
+ lstActionStatus = [];
2821
+ }
2822
+ /**
2823
+ * STAGE
2824
+ */
2825
+ class WorkflowStage extends WF_TemplateStage {
2826
+ x = 0;
2827
+ y = 0;
2828
+ isReverse;
2829
+ isReverseLabel;
2830
+ lstAction;
2831
+ drawerPosition;
2832
+ constructor(obj) {
2833
+ obj = obj || {};
2834
+ super(obj);
2835
+ this.x = obj.x;
2836
+ this.y = obj.y;
2837
+ this.isReverse = obj.isReverse;
2838
+ this.isReverseLabel = obj.isReverseLabel;
2839
+ this.lstAction = obj.lstAction;
2840
+ this.drawerPosition = obj.drawerPosition;
2841
+ }
2842
+ }
2843
+ /**
2844
+ * Action
2845
+ */
2846
+ class WorkflowAction extends WF_TemplateStageAction {
2847
+ NextStageStatus; // string
2848
+ FromStage;
2849
+ FromSide;
2850
+ ToStage;
2851
+ ToSide;
2852
+ allowBack; // true = 2 đầu, false/undefined = 1 đầu
2853
+ labelBack;
2854
+ points;
2855
+ isBackAction;
2856
+ }
2857
+ class WorkflowEditorComponent extends BaseComponent {
2858
+ //#region NGHIỆP VỤ
2859
+ lstOrg = [];
2860
+ lstTemplateType = [];
2861
+ lstTemplatePrint = [];
2862
+ template = new Workflow();
2863
+ lstStage = [
2864
+ new WorkflowStage({ Code: 'start', Name: 'Start', x: -35, y: 120, StageType: 'START', SeqValue: 1 }),
2865
+ new WorkflowStage({ Code: 'end', Name: 'End', x: 780, y: 120, StageType: 'END', SeqValue: 999 }),
2866
+ ];
2867
+ lstAction = [];
2868
+ onSave = new EventEmitter();
2869
+ //#endregion
2870
+ Math = Math;
2871
+ drawTemplateVisibel = false;
2872
+ settingVisible = false;
2873
+ wfcSetting = { ShowGrid: true };
2874
+ selectedStage;
2875
+ hoverStage;
2876
+ selectedAction;
2877
+ selectedBackAction;
2878
+ hoverEdge;
2879
+ // vẽ đường liên kết mới
2880
+ connectingFrom;
2881
+ connectingPoints = [];
2882
+ previewX = 0;
2883
+ previewY = 0;
2884
+ /** điểm đang chọn để di chuyển */
2885
+ draggingPoint;
2886
+ /** đang kéo chọn nhiều node */
2887
+ isSelecting = false;
2888
+ /** điểm bắt đầu kéo */
2889
+ selectStart;
2890
+ /** điểm kết thúc kéo */
2891
+ selectEnd;
2892
+ draggingGroup = false;
2893
+ dragStartMouse;
2894
+ dragStartPositions = new Map();
2895
+ dragStartEdgePoints = new Map();
2896
+ /** list các điểm đang được chọn */
2897
+ lstSelectedStageCode = new Set();
2898
+ // SNAP point
2899
+ gridSize = 20; // 10 / 20 / 25 tuỳ thích
2900
+ snapToGrid = true;
2901
+ // ===== drag node =====
2902
+ draggingNode;
2903
+ isDragging = false;
2904
+ dragStartX = 0;
2905
+ dragStartY = 0;
2906
+ offsetX = 0;
2907
+ offsetY = 0;
2908
+ KEY_MOVE_STEP = 5; // px
2909
+ KEY_MOVE_STEP_BIG = 20; // Shift + arrow
2910
+ // pan: dịch chuyển cả canvas
2911
+ isPanning = false;
2912
+ panStart;
2913
+ // panX = 0;
2914
+ // panY = 0;
2915
+ panX = -5000;
2916
+ panY = -5000;
2917
+ isMoveMode = true;
2918
+ canvasRef;
2919
+ svgRef;
2920
+ get normalEdges() {
2921
+ return this.lstAction.filter((e) => e !== this.hoverEdge && !e.isBackAction);
2922
+ }
2923
+ get hoverEdges() {
2924
+ if (this.hoverEdge && !this.hoverEdge.isBackAction) {
2925
+ return [this.hoverEdge];
2926
+ }
2927
+ if (this.selectedAction && !this.selectedAction.isBackAction) {
2928
+ return [this.selectedAction];
2929
+ }
2930
+ return [];
2931
+ }
2932
+ axisLock = null;
2933
+ onKeyDown(e) {
2934
+ // bỏ qua nếu đang gõ input
2935
+ const target = e.target;
2936
+ if (['INPUT', 'TEXTAREA'].includes(target.tagName))
2937
+ return;
2938
+ if (e.key === 'Escape') {
2939
+ this.cancelConnecting();
2940
+ }
2941
+ if (e.ctrlKey && e.key === 'z') {
2942
+ console.log('ctrl z');
2943
+ e.preventDefault();
2944
+ this.undo();
2945
+ }
2946
+ if (e.ctrlKey && (e.key === 'y' || (e.shiftKey && e.key === 'Z'))) {
2947
+ console.log('ctrl y');
2948
+ e.preventDefault();
2949
+ this.redo();
2950
+ }
2951
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].indexOf(e.key) >= 0) {
2952
+ let dx = 0;
2953
+ let dy = 0;
2954
+ const step = e.shiftKey ? this.KEY_MOVE_STEP_BIG : this.KEY_MOVE_STEP;
2955
+ switch (e.key) {
2956
+ case 'ArrowLeft':
2957
+ dx = -step;
2958
+ break;
2959
+ case 'ArrowRight':
2960
+ dx = step;
2961
+ break;
2962
+ case 'ArrowUp':
2963
+ dy = -step;
2964
+ break;
2965
+ case 'ArrowDown':
2966
+ dy = step;
2967
+ break;
2968
+ default:
2969
+ return;
2970
+ }
2971
+ e.preventDefault();
2972
+ this.moveSelectedNodesBy(dx, dy);
2973
+ }
2974
+ }
2975
+ ngOnInit() {
2976
+ console.log('ngOnInit', this.template, this.lstStage, this.lstAction);
2977
+ console.log('template', this.template);
2978
+ console.log('lstStage', this.lstStage);
2979
+ console.log('lstAction', this.lstAction);
2980
+ this.lstStage.forEach((x) => {
2981
+ x.lstAction = this.lstAction.filter((y) => y.FromStage == x.Code);
2982
+ });
2983
+ }
2984
+ ngAfterViewInit() {
2985
+ this.updateCanvasSize();
2986
+ }
2987
+ moveSelectedNodesBy(dx, dy) {
2988
+ if (this.lstSelectedStageCode.size === 0)
2989
+ return;
2990
+ // ⭐ UNDO SNAPSHOT
2991
+ this.pushUndo({
2992
+ type: 'move-nodes',
2993
+ before: this.cloneNodePositions(this.lstSelectedStageCode),
2994
+ });
2995
+ for (const n of this.lstStage) {
2996
+ if (!this.lstSelectedStageCode.has(n.Code))
2997
+ continue;
2998
+ n.x = this.clamp(n.x + dx, 0, this.CANVAS_WIDTH - this.NODE_WIDTH);
2999
+ n.y = this.clamp(n.y + dy, 0, this.CANVAS_HEIGHT - this.NODE_HEIGHT);
3000
+ }
3001
+ }
3002
+ save() {
3003
+ this.onSave.emit({ template: this.template, lstStage: this.lstStage, lstAction: this.lstAction });
3004
+ }
3005
+ addStage() {
3006
+ console.log('addStage');
3007
+ const code = 'New_Stage_' + Math.round(Math.random() * 1000);
3008
+ // tìm vị trí trống (đơn giản & hiệu quả)
3009
+ const baseX = 200;
3010
+ const baseY = 120;
3011
+ const GAP_X = 200;
3012
+ const GAP_Y = 120;
3013
+ let x = baseX;
3014
+ let y = baseY;
3015
+ while (this.isPositionOccupied(x, y)) {
3016
+ y += GAP_Y;
3017
+ }
3018
+ const stage = new WorkflowStage();
3019
+ stage.Code = code;
3020
+ stage.Name = 'New Stage';
3021
+ stage.x = x;
3022
+ stage.y = y;
3023
+ stage.StageType = 'NODE';
3024
+ stage.SeqValue = 10;
3025
+ this.lstStage.push(stage);
3026
+ this.lstStage = this.lstStage.sort((a, b) => a.SeqValue - b.SeqValue);
3027
+ // this.selectedStage = node;
3028
+ this.hoverStage = stage;
3029
+ }
3030
+ isConnectingFrom(node) {
3031
+ return this.connectingFrom?.node.Code === node.Code;
3032
+ }
3033
+ isDraggingFrom(node) {
3034
+ return this.draggingNode?.Code === node.Code;
3035
+ }
3036
+ isSelectedNode(node) {
3037
+ return this.selectedStage?.Code === node.Code;
3038
+ }
3039
+ cancelConnecting() {
3040
+ if (this.connectingFrom) {
3041
+ this.connectingFrom = undefined;
3042
+ this.previewX = 0;
3043
+ this.previewY = 0;
3044
+ this.connectingPoints = [];
3045
+ }
3046
+ }
3047
+ //#region NGHIỆP VỤ
3048
+ addAction() {
3049
+ if (!this.selectedStage)
3050
+ return;
3051
+ const action = new WorkflowAction();
3052
+ action.FromStage = this.selectedStage.Code;
3053
+ action.FromSide = 'right';
3054
+ this.selectedStage.lstAction = this.selectedStage.lstAction ? [...this.selectedStage.lstAction, action] : [action];
3055
+ this.lstAction = this.lstAction ? [...this.lstAction, action] : [action];
3056
+ }
3057
+ onchangeAllowBack(action) {
3058
+ console.log('onchangeAllowBack', action);
3059
+ const stage = this.lstStage.find((x) => x.Code == action.ToStage);
3060
+ if (!stage) {
3061
+ this.notiService.error('Stage not found');
3062
+ return;
3063
+ }
3064
+ if (action.allowBack) {
3065
+ const newAction = new WorkflowAction();
3066
+ newAction.FromStage = action.ToStage;
3067
+ newAction.FromSide = 'right';
3068
+ newAction.ToStage = action.FromStage;
3069
+ newAction.ToSide = 'left';
3070
+ newAction.isBackAction = true;
3071
+ console.log('newAction', newAction);
3072
+ this.lstAction = [...this.lstAction, newAction];
3073
+ stage.lstAction = [...stage.lstAction, newAction];
3074
+ this.selectedBackAction = newAction;
3075
+ }
3076
+ else {
3077
+ action.labelBack = '';
3078
+ const backAction = this.lstAction.find((x) => x.FromStage == action.ToStage && x.ToStage == action.FromStage && x.isBackAction);
3079
+ console.log('remove action', backAction);
3080
+ if (!backAction) {
3081
+ this.notiService.error('Action not found');
3082
+ }
3083
+ else {
3084
+ this.lstAction = [...this.lstAction.filter((x) => x != backAction)];
3085
+ stage.lstAction = [...stage.lstAction?.filter((x) => x != backAction)];
3086
+ this.selectedBackAction = undefined;
3087
+ }
3088
+ }
3089
+ }
3090
+ onchangeLabelBack(action) {
3091
+ const backAction = this.lstAction.find((x) => x.FromStage == action.ToStage && x.ToStage == action.FromStage && x.isBackAction);
3092
+ if (!backAction) {
3093
+ this.notiService.error('Action not found');
3094
+ }
3095
+ else {
3096
+ backAction.ActionText = action.labelBack || '';
3097
+ }
3098
+ }
3099
+ onchangeActionText(action) {
3100
+ if (action.isBackAction) {
3101
+ const goAction = this.lstAction.find((x) => x.FromStage == action.ToStage && x.ToStage == action.FromStage && !x.isBackAction);
3102
+ if (!goAction) {
3103
+ this.notiService.error('Action not found');
3104
+ }
3105
+ else {
3106
+ goAction.labelBack = action.ActionText || '';
3107
+ }
3108
+ }
3109
+ }
3110
+ onchangeNextStage(action) {
3111
+ console.log('onchangeNextStage', action);
3112
+ }
3113
+ async deleteNode() {
3114
+ if (!this.selectedStage)
3115
+ return;
3116
+ if (!(await this.confirm(`Xoá node: ${this.selectedStage.Name}`)))
3117
+ return;
3118
+ const node = this.selectedStage;
3119
+ // 🔹 snapshot edges liên quan
3120
+ const relatedEdges = this.lstAction.filter((e) => e.FromStage === node.Code || e.ToStage === node.Code);
3121
+ // 🔹 snapshot vị trí node trong mảng
3122
+ const nodeIndex = this.lstStage.findIndex((n) => n.Code === node.Code);
3123
+ // ⭐ PUSH UNDO
3124
+ this.pushUndo({
3125
+ type: 'delete-node',
3126
+ node: { ...node }, // clone
3127
+ nodeIndex,
3128
+ edges: relatedEdges.map((e) => ({ ...e })),
3129
+ });
3130
+ // ❌ xoá edges
3131
+ this.lstAction = this.lstAction.filter((e) => e.FromStage !== node.Code && e.ToStage !== node.Code);
3132
+ // ❌ xoá node
3133
+ this.lstStage.splice(nodeIndex, 1);
3134
+ this.selectedStage = undefined;
3135
+ }
3136
+ async deleteAction(action) {
3137
+ if (!(await this.confirm(`Delete action ???`)))
3138
+ return;
3139
+ const stage = this.lstStage.find((x) => x.Code == action.FromStage);
3140
+ if (!stage)
3141
+ return;
3142
+ stage.lstAction = stage.lstAction?.filter((x) => x != action);
3143
+ this.lstAction = this.lstAction.filter((x) => x != action);
3144
+ }
3145
+ //#endregion
3146
+ //#region Action type
3147
+ inputStageStatusVisible = false;
3148
+ inputStageStatusCode = '';
3149
+ inputStageStatusName = '';
3150
+ inputStageStatusElement;
3151
+ checkRemoveableStageStatus(tag) {
3152
+ if (this.lstAction.find((x) => x.StageStatus === tag.Code) ||
3153
+ this.lstAction.find((x) => x.NextStageStatus === tag.Code)) {
3154
+ return false;
3155
+ }
3156
+ return true;
3157
+ }
3158
+ handleCloseStageStatus(removedTag) {
3159
+ this.template.lstStageStatus = this.template.lstStageStatus.filter((tag) => tag !== removedTag);
3160
+ }
3161
+ showInputStageStatus() {
3162
+ this.inputStageStatusVisible = true;
3163
+ setTimeout(() => {
3164
+ this.inputStageStatusElement?.nativeElement.focus();
3165
+ }, 10);
3166
+ }
3167
+ handleInputStageStatusConfirm() {
3168
+ if (this.inputStageStatusCode && !this.template.lstStageStatus.find((x) => x.Code === this.inputValue)) {
3169
+ this.template.lstStageStatus = [
3170
+ ...this.template.lstStageStatus,
3171
+ { Code: this.inputStageStatusCode, Name: this.inputStageStatusName },
3172
+ ];
3173
+ }
3174
+ this.inputStageStatusCode = '';
3175
+ this.inputStageStatusName = '';
3176
+ this.inputStageStatusVisible = false;
3177
+ }
3178
+ //#endregion
3179
+ //#region Action type
3180
+ sliceTagName(tag) {
3181
+ // const isLongTag = tag.length > 20;
3182
+ // return isLongTag ? `${tag.slice(0, 20)}...` : tag;
3183
+ return tag;
3184
+ }
3185
+ inputActionTypeVisible = false;
3186
+ inputActionTypeCode = '';
3187
+ inputActionTypeName = '';
3188
+ inputActionTypeElement;
3189
+ checkRemoveableActionType(tag) {
3190
+ if (this.lstAction.find((x) => x.ActionType === tag.Code)) {
3191
+ return false;
3192
+ }
3193
+ return true;
3194
+ }
3195
+ handleCloseActionType(removedTag) {
3196
+ this.template.lstActionType = this.template.lstActionType.filter((tag) => tag !== removedTag);
3197
+ }
3198
+ showInputActionType() {
3199
+ this.inputActionTypeVisible = true;
3200
+ setTimeout(() => {
3201
+ console.log(this.inputActionTypeElement);
3202
+ this.inputActionTypeElement?.nativeElement.focus();
3203
+ }, 10);
3204
+ }
3205
+ handleInputActionTypeConfirm() {
3206
+ if (this.inputActionTypeCode && !this.template.lstActionType.find((x) => x.Code === this.inputValue)) {
3207
+ this.template.lstActionType = [
3208
+ ...this.template.lstActionType,
3209
+ { Code: this.inputActionTypeCode, Name: this.inputActionTypeName },
3210
+ ];
3211
+ }
3212
+ this.inputActionTypeCode = '';
3213
+ this.inputActionTypeName = '';
3214
+ this.inputActionTypeVisible = false;
3215
+ }
3216
+ //#endregion
3217
+ //#region Action status
3218
+ inputVisible = false;
3219
+ inputValue = '';
3220
+ inputElement;
3221
+ checkRemoveableActionStatus(tag) {
3222
+ if (this.lstAction.find((x) => x.ActionStatus === tag.Code)) {
3223
+ return false;
3224
+ }
3225
+ return true;
3226
+ }
3227
+ handleClose(removedTag) {
3228
+ this.template.lstActionStatus = this.template.lstActionStatus.filter((tag) => tag !== removedTag);
3229
+ }
3230
+ showInput() {
3231
+ this.inputVisible = true;
3232
+ setTimeout(() => {
3233
+ console.log(this.inputElement);
3234
+ this.inputElement?.nativeElement.focus();
3235
+ }, 10);
3236
+ }
3237
+ handleInputConfirm() {
3238
+ if (this.inputValue && !this.template.lstActionStatus.find((x) => x.Code === this.inputValue)) {
3239
+ this.template.lstActionStatus = [...this.template.lstActionStatus, { Code: this.inputValue }];
3240
+ }
3241
+ this.inputValue = '';
3242
+ this.inputVisible = false;
3243
+ }
3244
+ //#endregion
3245
+ //#region Canvas action
3246
+ onMouseDownCanvas(event) {
3247
+ console.log('onMouseDownCanvas > event.button', event.button);
3248
+ if (event.button !== 0)
3249
+ return; // button === 0 : chuột trái
3250
+ // click vào canvas trống
3251
+ if (event.target.closest('.workflow-node'))
3252
+ return;
3253
+ // 🖐 SPACE + drag → pan
3254
+ if (this.isMoveMode) {
3255
+ this.isPanning = true;
3256
+ this.panStart = {
3257
+ x: event.clientX,
3258
+ y: event.clientY,
3259
+ };
3260
+ this.canvasRef?.nativeElement.classList.add('panning');
3261
+ return;
3262
+ }
3263
+ const p = this.getSvgPoint(event, true);
3264
+ this.isSelecting = true;
3265
+ this.selectStart = p;
3266
+ this.selectEnd = p;
3267
+ this.lstSelectedStageCode.clear();
3268
+ }
3269
+ onMouseMoveCanvas(event) {
3270
+ if (this.isPanning && this.panStart) {
3271
+ const dx = event.clientX - this.panStart.x;
3272
+ const dy = event.clientY - this.panStart.y;
3273
+ this.panX += dx;
3274
+ this.panY += dy;
3275
+ this.panStart = {
3276
+ x: event.clientX,
3277
+ y: event.clientY,
3278
+ };
3279
+ return;
3280
+ }
3281
+ const p = this.getSvgPoint(event);
3282
+ const dx = Math.abs(event.clientX - this.dragStartX);
3283
+ const dy = Math.abs(event.clientY - this.dragStartY);
3284
+ if (dx > 4 || dy > 4) {
3285
+ this.isDragging = true;
3286
+ }
3287
+ // 1️⃣ đang kéo vùng chọn
3288
+ if (this.isSelecting && this.selectStart) {
3289
+ const p1 = this.getSvgPoint(event, true);
3290
+ this.selectEnd = p1;
3291
+ this.updateSelection();
3292
+ return;
3293
+ }
3294
+ // 2️⃣ đang kéo nhiều node
3295
+ if (this.draggingGroup && this.dragStartMouse) {
3296
+ let dx = p.x - this.dragStartMouse.x;
3297
+ let dy = p.y - this.dragStartMouse.y;
3298
+ // ⭐ xác định trục lock
3299
+ if (event.shiftKey && !this.axisLock) {
3300
+ this.axisLock = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
3301
+ }
3302
+ if (event.shiftKey && this.axisLock === 'x') {
3303
+ dy = 0;
3304
+ }
3305
+ if (event.shiftKey && this.axisLock === 'y') {
3306
+ dx = 0;
3307
+ }
3308
+ const snap = this.snapPoint({
3309
+ x: this.dragStartMouse.x + dx,
3310
+ y: this.dragStartMouse.y + dy,
3311
+ }, event);
3312
+ const sdx = snap.x - this.dragStartMouse.x;
3313
+ const sdy = snap.y - this.dragStartMouse.y;
3314
+ // for (const n of this.lstStage) {
3315
+ // if (!this.lstSelectedStageCode.has(n.Code)) continue;
3316
+ // const start = this.dragStartPositions.get(n.Code)!;
3317
+ // n.x = start.x + sdx;
3318
+ // n.y = start.y + sdy;
3319
+ // }
3320
+ const nodeW = this.NODE_WIDTH;
3321
+ const nodeH = this.NODE_HEIGHT;
3322
+ // tìm biên group
3323
+ let minX = Infinity;
3324
+ let minY = Infinity;
3325
+ let maxX = -Infinity;
3326
+ let maxY = -Infinity;
3327
+ for (const n of this.lstStage) {
3328
+ if (!this.lstSelectedStageCode.has(n.Code))
3329
+ continue;
3330
+ const start = this.dragStartPositions.get(n.Code);
3331
+ minX = Math.min(minX, start.x);
3332
+ minY = Math.min(minY, start.y);
3333
+ maxX = Math.max(maxX, start.x + nodeW);
3334
+ maxY = Math.max(maxY, start.y + nodeH);
3335
+ }
3336
+ // clamp delta
3337
+ const clampedDx = this.clamp(sdx, -minX, this.CANVAS_WIDTH - maxX);
3338
+ const clampedDy = this.clamp(sdy, -minY, this.CANVAS_HEIGHT - maxY);
3339
+ // 🔹 move nodes
3340
+ for (const n of this.lstStage) {
3341
+ if (!this.lstSelectedStageCode.has(n.Code))
3342
+ continue;
3343
+ const start = this.dragStartPositions.get(n.Code);
3344
+ n.x = start.x + clampedDx;
3345
+ n.y = start.y + clampedDy;
3346
+ }
3347
+ // 🔹 move edge points
3348
+ for (const [edge, startPoints] of this.dragStartEdgePoints.entries()) {
3349
+ if (!edge.points)
3350
+ continue;
3351
+ edge.points.forEach((pt, i) => {
3352
+ pt.x = startPoints[i].x + dx;
3353
+ pt.y = startPoints[i].y + dy;
3354
+ });
3355
+ }
3356
+ return;
3357
+ }
3358
+ // 4️⃣ đang kéo 1 node
3359
+ if (this.draggingNode) {
3360
+ const dx = p.x;
3361
+ const dy = p.y;
3362
+ // ⭐ xác định trục khi giữ Shift
3363
+ if (event.shiftKey && !this.axisLock) {
3364
+ if (Math.abs(dx) > Math.abs(dy)) {
3365
+ this.axisLock = 'x';
3366
+ }
3367
+ else {
3368
+ this.axisLock = 'y';
3369
+ }
3370
+ }
3371
+ let rawX = p.x - this.offsetX;
3372
+ let rawY = p.y - this.offsetY;
3373
+ // ⭐ khoá trục
3374
+ if (event.shiftKey && this.axisLock === 'x') {
3375
+ rawY = this.draggingNode.y;
3376
+ }
3377
+ if (event.shiftKey && this.axisLock === 'y') {
3378
+ rawX = this.draggingNode.x;
3379
+ }
3380
+ const snap = this.snapPoint({ x: rawX, y: rawY }, event);
3381
+ // this.draggingNode.x = snap.x;
3382
+ // this.draggingNode.y = snap.y;
3383
+ const nodeW = this.NODE_WIDTH;
3384
+ const nodeH = this.NODE_HEIGHT;
3385
+ this.draggingNode.x = this.clamp(snap.x, 0, this.CANVAS_WIDTH - nodeW);
3386
+ this.draggingNode.y = this.clamp(snap.y, 0, this.CANVAS_HEIGHT - nodeH);
3387
+ return;
3388
+ }
3389
+ // 3️⃣ đang kéo waypoint
3390
+ if (this.draggingPoint) {
3391
+ const p1 = this.getSvgPoint(event, true);
3392
+ const snap = this.snapPoint(p1, event);
3393
+ // this.draggingPoint.x = snap.x;
3394
+ // this.draggingPoint.y = snap.y;
3395
+ this.draggingPoint.x = this.clamp(snap.x, 0, this.CANVAS_WIDTH);
3396
+ this.draggingPoint.y = this.clamp(snap.y, 0, this.CANVAS_HEIGHT);
3397
+ return;
3398
+ }
3399
+ // 5️⃣ đang kéo connection preview
3400
+ if (this.connectingFrom) {
3401
+ const p = this.getSvgPoint(event, true);
3402
+ this.previewX = p.x;
3403
+ this.previewY = p.y;
3404
+ }
3405
+ }
3406
+ onMouseUpCanvas(event) {
3407
+ this.isSelecting = false;
3408
+ this.draggingGroup = false;
3409
+ this.draggingNode = undefined;
3410
+ this.draggingPoint = undefined;
3411
+ this.isPanning = false;
3412
+ setTimeout(() => (this.isDragging = false));
3413
+ }
3414
+ onClickCanvas(e) {
3415
+ console.log('onClickCanvas', e.offsetX, e.offsetY);
3416
+ if (!this.connectingFrom)
3417
+ return;
3418
+ this.connectingPoints.push({
3419
+ x: e.offsetX,
3420
+ y: e.offsetY,
3421
+ });
3422
+ console.log(this.connectingPoints);
3423
+ }
3424
+ //#endregion
3425
+ //#region NODE action
3426
+ onMouseDownNode(event, node) {
3427
+ console.log('onMouseDownNode', node);
3428
+ event.stopPropagation();
3429
+ event.preventDefault();
3430
+ this.axisLock = null;
3431
+ const p = this.getSvgPoint(event);
3432
+ // CASE 1️⃣: node nằm trong selection → kéo cả group
3433
+ if (this.lstSelectedStageCode.has(node.Code)) {
3434
+ /* =============================
3435
+ * UNDO SNAPSHOT (GROUP)
3436
+ * ============================= */
3437
+ this.pushUndo({
3438
+ type: 'move-nodes',
3439
+ before: this.cloneNodePositions(this.lstSelectedStageCode),
3440
+ });
3441
+ this.draggingGroup = true;
3442
+ this.dragStartMouse = p;
3443
+ // 🔹 snapshot node positions
3444
+ this.dragStartPositions.clear();
3445
+ for (const n of this.lstStage) {
3446
+ if (this.lstSelectedStageCode.has(n.Code)) {
3447
+ this.dragStartPositions.set(n.Code, { x: n.x, y: n.y });
3448
+ }
3449
+ }
3450
+ // 🔹 snapshot edge points
3451
+ this.dragStartEdgePoints = new Map();
3452
+ for (const e of this.lstAction) {
3453
+ if (!e.points?.length)
3454
+ continue;
3455
+ if (this.lstSelectedStageCode.has(e.FromStage || '') || this.lstSelectedStageCode.has(e.ToStage || '')) {
3456
+ this.dragStartEdgePoints.set(e, e.points.map((p) => ({ x: p.x, y: p.y })));
3457
+ }
3458
+ }
3459
+ return;
3460
+ }
3461
+ // CASE 2️⃣: node chưa được chọn → kéo node đơn
3462
+ this.lstSelectedStageCode.clear();
3463
+ // this.selectedNodeIds.add(node.id);
3464
+ /* =============================
3465
+ * UNDO SNAPSHOT (1 NODE)
3466
+ * ============================= */
3467
+ this.pushUndo({
3468
+ type: 'move-nodes',
3469
+ before: this.cloneNodePositions(new Set([node.Code])),
3470
+ });
3471
+ this.draggingNode = node;
3472
+ this.dragStartX = event.clientX;
3473
+ this.dragStartY = event.clientY;
3474
+ this.offsetX = event.clientX - node.x;
3475
+ this.offsetY = event.clientY - node.y;
3476
+ this.isDragging = false;
3477
+ }
3478
+ onClickStage(node) {
3479
+ console.log('onClickStage', node);
3480
+ this.hoverStage = undefined;
3481
+ if (this.isDragging)
3482
+ return;
3483
+ if (this.connectingFrom) {
3484
+ if (this.connectingFrom.node.Code !== node.Code) {
3485
+ const existed = this.lstAction.some((e) => e.FromStage === this.connectingFrom.node.Code && e.ToStage === node.Code);
3486
+ if (!existed) {
3487
+ const newAction = new WorkflowAction();
3488
+ newAction.FromStage = this.connectingFrom.node.Code;
3489
+ newAction.FromSide = this.connectingFrom.side;
3490
+ newAction.ToStage = node.Code;
3491
+ newAction.ToSide = 'left';
3492
+ // ⭐ NẾU CÓ WAYPOINT → MANUAL ROUTE
3493
+ if (this.connectingPoints.length > 0) {
3494
+ newAction.points = [...this.connectingPoints];
3495
+ }
3496
+ this.lstAction.push(newAction);
3497
+ const stage = this.lstStage.find((x) => x.Code == newAction.FromStage);
3498
+ if (stage) {
3499
+ stage.lstAction = stage.lstAction ? [...stage.lstAction, newAction] : [newAction];
3500
+ }
3501
+ }
3502
+ }
3503
+ this.cancelConnecting();
3504
+ return;
3505
+ }
3506
+ if (this.isDragging)
3507
+ return;
3508
+ if (this.lstSelectedStageCode.size)
3509
+ return;
3510
+ // if (node.id == 'start' || node.id == 'end') return;
3511
+ this.selectedStage = node;
3512
+ console.log(this.selectedStage);
3513
+ }
3514
+ //#endregion
3515
+ //#region EDGE action
3516
+ onMouseEnterEdge(e) {
3517
+ // console.log('onMouseEnterEdge', e);
3518
+ if (this.connectingFrom)
3519
+ return;
3520
+ this.hoverEdge = e;
3521
+ this.connectingPoints = e.points || [];
3522
+ }
3523
+ onMouseLeaveEdge(e) {
3524
+ // console.log('onEdgeLeave', e);
3525
+ if (this.connectingFrom)
3526
+ return;
3527
+ this.hoverEdge = undefined;
3528
+ this.connectingPoints = [];
3529
+ }
3530
+ edgeClickTimer = null;
3531
+ CLICK_DELAY = 250;
3532
+ onClickEdge(edge, event, doAction = false) {
3533
+ event.stopPropagation();
3534
+ event.preventDefault();
3535
+ if (this.edgeClickTimer) {
3536
+ // 👉 DBL CLICK
3537
+ clearTimeout(this.edgeClickTimer);
3538
+ this.edgeClickTimer = null;
3539
+ this.onDoubleClickEdge(event, edge);
3540
+ return;
3541
+ }
3542
+ if (doAction) {
3543
+ this.selectedAction = edge;
3544
+ this.selectedBackAction = this.lstAction.find((x) => x.isBackAction == true &&
3545
+ x.FromStage == this.selectedAction?.ToStage &&
3546
+ x.ToStage == this.selectedAction?.FromStage);
3547
+ }
3548
+ else {
3549
+ // 👉 SINGLE CLICK (chờ xem có dbl không)
3550
+ this.edgeClickTimer = setTimeout(() => {
3551
+ this.edgeClickTimer = null;
3552
+ console.log('onClickEdge', edge);
3553
+ this.onClickEdge(edge, event, true);
3554
+ }, this.CLICK_DELAY);
3555
+ }
3556
+ }
3557
+ onDoubleClickEdge(event, edge) {
3558
+ console.log('onDoubleClickEdge', edge);
3559
+ event.stopPropagation();
3560
+ event.preventDefault();
3561
+ const p = this.getSvgPoint(event, true);
3562
+ // Nếu edge chưa có manual points → convert auto → manual
3563
+ if (!edge.points || edge.points.length === 0) {
3564
+ edge.points = [];
3565
+ }
3566
+ const insertIndex = this.findInsertIndex(edge, p);
3567
+ edge.points.splice(insertIndex, 0, {
3568
+ x: p.x,
3569
+ y: p.y,
3570
+ });
3571
+ }
3572
+ //#endregion
3573
+ //#region Connector action
3574
+ onClickConnector(event, node, side) {
3575
+ console.log('onClickConnector', side, node);
3576
+ event.stopPropagation();
3577
+ event.preventDefault();
3578
+ const p = this.getSvgPoint(event, true);
3579
+ this.previewX = p.x;
3580
+ this.previewY = p.y;
3581
+ /* =============================
3582
+ * CLICK OUT (RIGHT) → BẮT ĐẦU
3583
+ * ============================= */
3584
+ if (side === 'right') {
3585
+ this.connectingFrom = {
3586
+ node: node,
3587
+ side: side,
3588
+ };
3589
+ this.connectingPoints = []; // ⭐ reset waypoint
3590
+ return;
3591
+ }
3592
+ /* =============================
3593
+ * CLICK IN (LEFT) → KẾT THÚC
3594
+ * ============================= */
3595
+ if (side === 'left' && this.connectingFrom) {
3596
+ if (this.connectingFrom.node.Code !== node.Code) {
3597
+ const existed = this.lstAction.some((e) => e.FromStage === this.connectingFrom.node.Code && e.ToStage === node.Code);
3598
+ if (!existed) {
3599
+ const newAction = new WorkflowAction();
3600
+ newAction.FromStage = this.connectingFrom.node.Code;
3601
+ newAction.FromSide = this.connectingFrom.side;
3602
+ newAction.ToStage = node.Code;
3603
+ newAction.ToSide = 'left';
3604
+ // ⭐ NẾU CÓ WAYPOINT → MANUAL ROUTE
3605
+ if (this.connectingPoints.length > 0) {
3606
+ newAction.points = [...this.connectingPoints];
3607
+ }
3608
+ this.lstAction.push(newAction);
3609
+ const stage = this.lstStage.find((x) => x.Code == newAction.FromStage);
3610
+ if (stage) {
3611
+ stage.lstAction = stage.lstAction ? [...stage.lstAction, newAction] : [newAction];
3612
+ }
3613
+ console.log(newAction);
3614
+ console.log(stage);
3615
+ }
3616
+ }
3617
+ this.cancelConnecting();
3618
+ }
3619
+ }
3620
+ //#endregion
3621
+ //#region Point action
3622
+ onmousedownPoint(event, p) {
3623
+ console.log('onmousedownPoint', p);
3624
+ event.stopPropagation();
3625
+ event.preventDefault();
3626
+ if (!this.draggingPoint) {
3627
+ this.draggingPoint = p;
3628
+ }
3629
+ else {
3630
+ this.draggingPoint = undefined;
3631
+ }
3632
+ }
3633
+ onRightClickPoint(event, edge, index) {
3634
+ console.log('onRightClickPoint', edge, index);
3635
+ event.preventDefault();
3636
+ event.stopPropagation();
3637
+ if (!edge.points)
3638
+ return;
3639
+ edge.points.splice(index, 1);
3640
+ // Nếu xoá hết → quay về auto route
3641
+ if (edge.points.length === 0) {
3642
+ delete edge.points;
3643
+ }
3644
+ }
3645
+ //#endregion
3646
+ //#region UNDO REDU
3647
+ undoStack = [];
3648
+ redoStack = [];
3649
+ cloneNodePositions(nodeIds) {
3650
+ const map = new Map();
3651
+ for (const n of this.lstStage) {
3652
+ if (nodeIds.has(n.Code)) {
3653
+ map.set(n.Code, { x: n.x, y: n.y });
3654
+ }
3655
+ }
3656
+ return map;
3657
+ }
3658
+ pushAlignUndo() {
3659
+ this.pushUndo({
3660
+ type: 'move-nodes',
3661
+ before: this.cloneNodePositions(this.lstSelectedStageCode),
3662
+ });
3663
+ }
3664
+ pushUndo(action) {
3665
+ this.undoStack.push(action);
3666
+ this.redoStack = [];
3667
+ }
3668
+ undo() {
3669
+ const action = this.undoStack.pop();
3670
+ if (!action)
3671
+ return;
3672
+ switch (action.type) {
3673
+ case 'move-nodes':
3674
+ this.undoMoveNodes(action);
3675
+ break;
3676
+ case 'delete-node': {
3677
+ // 🔙 restore node
3678
+ this.lstStage.splice(action.nodeIndex, 0, action.node);
3679
+ // 🔙 restore edges
3680
+ this.lstAction.push(...action.edges);
3681
+ // redo snapshot
3682
+ this.redoStack.push(action);
3683
+ break;
3684
+ }
3685
+ }
3686
+ }
3687
+ undoMoveNodes(action) {
3688
+ const after = this.cloneNodePositions(new Set(action.before.keys()));
3689
+ this.redoStack.push({
3690
+ type: 'move-nodes',
3691
+ before: after,
3692
+ });
3693
+ // restore
3694
+ action.before.forEach((pos, id) => {
3695
+ const node = this.lstStage.find((n) => n.Code === id);
3696
+ if (node) {
3697
+ node.x = pos.x;
3698
+ node.y = pos.y;
3699
+ }
3700
+ });
3701
+ }
3702
+ redo() {
3703
+ const action = this.redoStack.pop();
3704
+ if (!action)
3705
+ return;
3706
+ switch (action.type) {
3707
+ case 'move-nodes':
3708
+ this.redoMoveNodes(action);
3709
+ break;
3710
+ case 'delete-node': {
3711
+ const nodeId = action.node.id;
3712
+ // redo = xoá lại
3713
+ this.lstAction = this.lstAction.filter((e) => e.FromStage !== nodeId && e.ToStage !== nodeId);
3714
+ const idx = this.lstStage.findIndex((n) => n.Code === nodeId);
3715
+ if (idx >= 0)
3716
+ this.lstStage.splice(idx, 1);
3717
+ this.undoStack.push(action);
3718
+ break;
3719
+ }
3720
+ }
3721
+ }
3722
+ redoMoveNodes(action) {
3723
+ const after = this.cloneNodePositions(new Set(action.before.keys()));
3724
+ this.undoStack.push({
3725
+ type: 'move-nodes',
3726
+ before: after,
3727
+ });
3728
+ action.before.forEach((pos, id) => {
3729
+ const node = this.lstStage.find((n) => n.Code === id);
3730
+ if (node) {
3731
+ node.x = pos.x;
3732
+ node.y = pos.y;
3733
+ }
3734
+ });
3735
+ }
3736
+ //#endregion
3737
+ CANVAS_WIDTH = 10000;
3738
+ CANVAS_HEIGHT = 10000;
3739
+ canvasWidth = 0;
3740
+ canvasHeight = 0;
3741
+ NODE_WIDTH = 160;
3742
+ NODE_HEIGHT = 80;
3743
+ //#region ALIGN
3744
+ getSelectedNodes() {
3745
+ return this.lstStage.filter((n) => this.lstSelectedStageCode.has(n.Code));
3746
+ }
3747
+ alignLeft() {
3748
+ if (this.lstSelectedStageCode.size < 2)
3749
+ return;
3750
+ this.pushAlignUndo();
3751
+ const nodes = this.getSelectedNodes();
3752
+ const minX = Math.min(...nodes.map((n) => n.x));
3753
+ nodes.forEach((n) => (n.x = minX));
3754
+ }
3755
+ alignCenter() {
3756
+ if (this.lstSelectedStageCode.size < 2)
3757
+ return;
3758
+ this.pushAlignUndo();
3759
+ const nodes = this.getSelectedNodes();
3760
+ const centers = nodes.map((n) => n.x + this.NODE_WIDTH / 2);
3761
+ const centerX = centers.reduce((a, b) => a + b) / centers.length;
3762
+ nodes.forEach((n) => {
3763
+ n.x = centerX - this.NODE_WIDTH / 2;
3764
+ });
3765
+ }
3766
+ alignRight() {
3767
+ if (this.lstSelectedStageCode.size < 2)
3768
+ return;
3769
+ this.pushAlignUndo();
3770
+ const nodes = this.getSelectedNodes();
3771
+ const maxX = Math.max(...nodes.map((n) => n.x + this.NODE_WIDTH));
3772
+ nodes.forEach((n) => (n.x = maxX - this.NODE_WIDTH));
3773
+ }
3774
+ alignTop() {
3775
+ if (this.lstSelectedStageCode.size < 2)
3776
+ return;
3777
+ this.pushAlignUndo();
3778
+ const nodes = this.getSelectedNodes();
3779
+ const minY = Math.min(...nodes.map((n) => n.y));
3780
+ nodes.forEach((n) => (n.y = minY));
3781
+ }
3782
+ alignMiddle() {
3783
+ if (this.lstSelectedStageCode.size < 2)
3784
+ return;
3785
+ this.pushAlignUndo();
3786
+ const nodes = this.getSelectedNodes();
3787
+ const centers = nodes.map((n) => n.y + this.NODE_HEIGHT / 2);
3788
+ const centerY = centers.reduce((a, b) => a + b) / centers.length;
3789
+ nodes.forEach((n) => {
3790
+ n.y = centerY - this.NODE_HEIGHT / 2;
3791
+ });
3792
+ }
3793
+ alignBottom() {
3794
+ if (this.lstSelectedStageCode.size < 2)
3795
+ return;
3796
+ this.pushAlignUndo();
3797
+ const nodes = this.getSelectedNodes();
3798
+ const maxY = Math.max(...nodes.map((n) => n.y + this.NODE_HEIGHT));
3799
+ nodes.forEach((n) => (n.y = maxY - this.NODE_HEIGHT));
3800
+ }
3801
+ distributeHorizontal() {
3802
+ if (this.lstSelectedStageCode.size < 3)
3803
+ return;
3804
+ this.pushAlignUndo();
3805
+ const nodes = this.getSelectedNodes().sort((a, b) => a.x - b.x);
3806
+ const first = nodes[0];
3807
+ const last = nodes[nodes.length - 1];
3808
+ const totalWidth = nodes.reduce((s, n) => s + this.NODE_WIDTH, 0);
3809
+ const space = (last.x - first.x - totalWidth + this.NODE_WIDTH) / (nodes.length - 1);
3810
+ let x = first.x;
3811
+ nodes.forEach((n, i) => {
3812
+ if (i === 0 || i === nodes.length - 1) {
3813
+ x = n.x + this.NODE_WIDTH + space;
3814
+ return;
3815
+ }
3816
+ n.x = x;
3817
+ x += this.NODE_WIDTH + space;
3818
+ });
3819
+ }
3820
+ distributeVertical() {
3821
+ if (this.lstSelectedStageCode.size < 3)
3822
+ return;
3823
+ this.pushAlignUndo();
3824
+ const nodes = this.getSelectedNodes().sort((a, b) => a.y - b.y);
3825
+ const first = nodes[0];
3826
+ const last = nodes[nodes.length - 1];
3827
+ const totalHeight = nodes.reduce((s, n) => s + this.NODE_HEIGHT, 0);
3828
+ const space = (last.y - first.y - totalHeight + this.NODE_HEIGHT) / (nodes.length - 1);
3829
+ let y = first.y;
3830
+ nodes.forEach((n, i) => {
3831
+ if (i === 0 || i === nodes.length - 1) {
3832
+ y = n.y + this.NODE_HEIGHT + space;
3833
+ return;
3834
+ }
3835
+ n.y = y;
3836
+ y += this.NODE_HEIGHT + space;
3837
+ });
3838
+ }
3839
+ //#endregion
3840
+ //#region zoom
3841
+ zoom = 1;
3842
+ minZoom = 0.5;
3843
+ maxZoom = 2;
3844
+ zoomStep = 0.1;
3845
+ get svgTransform() {
3846
+ const cx = this.getCanvasWidth() / 2;
3847
+ const cy = this.getCanvasHeight() / 2;
3848
+ return `
3849
+ translate(${cx} ${cy})
3850
+ scale(${this.zoom})
3851
+ translate(${-cx} ${-cy})
3852
+ `;
3853
+ }
3854
+ zoomIn() {
3855
+ this.zoom = Math.min(this.zoom + this.zoomStep, this.maxZoom);
3856
+ }
3857
+ zoomOut() {
3858
+ this.zoom = Math.max(this.zoom - this.zoomStep, this.minZoom);
3859
+ }
3860
+ //#endregion
3861
+ //#region HELPER
3862
+ isEdgeReversed(edge) {
3863
+ const from = this.getNodeCenter(this.lstStage.find((x) => x.Code == edge.FromStage), 'right');
3864
+ const to = this.getNodeCenter(this.lstStage.find((x) => x.Code == edge.ToStage), 'left');
3865
+ return to.x < from.x;
3866
+ }
3867
+ getEdgeLabelPosition(edge) {
3868
+ // Ưu tiên waypoint giữa nếu có
3869
+ if (edge.points && edge.points.length > 0) {
3870
+ const mid = edge.points[Math.floor(edge.points.length / 2)];
3871
+ return { x: mid.x, y: mid.y };
3872
+ }
3873
+ // fallback: giữa from → to
3874
+ const from = this.getNodeCenter(this.lstStage.find((x) => x.Code == edge.FromStage), 'right');
3875
+ const to = this.getNodeCenter(this.lstStage.find((x) => x.Code == edge.ToStage), 'left');
3876
+ return {
3877
+ x: (from.x + to.x) / 2,
3878
+ y: (from.y + to.y) / 2,
3879
+ };
3880
+ }
3881
+ getNodeCenter(node, side = 'right') {
3882
+ // const width = node.width ?? 160; // ⭐ đúng với card của bạn
3883
+ // const height = node.height ?? 80;
3884
+ const width = 160;
3885
+ const height = 80;
3886
+ switch (side) {
3887
+ case 'left':
3888
+ return {
3889
+ x: node.x,
3890
+ y: node.y + height / 2,
3891
+ };
3892
+ case 'right':
3893
+ return {
3894
+ x: node.x + width,
3895
+ y: node.y + height / 2,
3896
+ };
3897
+ case 'top':
3898
+ return {
3899
+ x: node.x + width / 2,
3900
+ y: node.y,
3901
+ };
3902
+ case 'bottom':
3903
+ return {
3904
+ x: node.x + width / 2,
3905
+ y: node.y + height,
3906
+ };
3907
+ }
3908
+ }
3909
+ updateCanvasSize() {
3910
+ const rect = this.canvasRef?.nativeElement.getBoundingClientRect();
3911
+ this.canvasWidth = rect?.width;
3912
+ this.canvasHeight = rect?.height;
3913
+ }
3914
+ clamp(value, min, max) {
3915
+ return Math.max(min, Math.min(max, value));
3916
+ }
3917
+ findInsertIndex(edge, p) {
3918
+ const points = this.getEdgeFullPoints(edge);
3919
+ if (!points)
3920
+ return 0;
3921
+ let minDist = Infinity;
3922
+ let index = 0;
3923
+ for (let i = 0; i < points.length - 1; i++) {
3924
+ const d = this.pointToSegmentDistance(p, points[i], points[i + 1]);
3925
+ if (d < minDist) {
3926
+ minDist = d;
3927
+ index = i;
3928
+ }
3929
+ }
3930
+ // index tương ứng với vị trí trong edge.points
3931
+ return Math.max(0, index);
3932
+ }
3933
+ getEdgeFullPoints(edge) {
3934
+ if (!edge.FromStage || !edge.ToStage)
3935
+ return null;
3936
+ const from = this.getNode(edge.FromStage);
3937
+ const to = this.getNode(edge.ToStage);
3938
+ return [{ x: from.x + 160, y: from.y + 40 }, ...(edge.points || []), { x: to.x, y: to.y + 40 }];
3939
+ }
3940
+ pointToSegmentDistance(p, a, b) {
3941
+ const dx = b.x - a.x;
3942
+ const dy = b.y - a.y;
3943
+ if (dx === 0 && dy === 0) {
3944
+ return Math.hypot(p.x - a.x, p.y - a.y);
3945
+ }
3946
+ const t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / (dx * dx + dy * dy);
3947
+ const clamped = Math.max(0, Math.min(1, t));
3948
+ const proj = {
3949
+ x: a.x + clamped * dx,
3950
+ y: a.y + clamped * dy,
3951
+ };
3952
+ return Math.hypot(p.x - proj.x, p.y - proj.y);
3953
+ }
3954
+ isPositionOccupied(x, y) {
3955
+ return this.lstStage.some((n) => Math.abs(n.x - x) < 160 && Math.abs(n.y - y) < 80);
3956
+ }
3957
+ getCanvasWidth() {
3958
+ // return 3000;
3959
+ const PADDING = 200;
3960
+ const MIN_WIDTH = 800;
3961
+ const maxX = Math.max(...this.lstStage.map((n) => (n.StageType == 'END' ? n.x : n.x + 160)), // 160 = node width
3962
+ MIN_WIDTH);
3963
+ return maxX + PADDING;
3964
+ }
3965
+ getCanvasHeight() {
3966
+ // return 2000;
3967
+ const PADDING = 100;
3968
+ const maxY = Math.max(...this.lstStage.map((n) => n.y + 80), 400);
3969
+ return maxY + PADDING;
3970
+ }
3971
+ /* =============================
3972
+ * CONVERT MOUSE → SVG COORD
3973
+ * ============================= */
3974
+ getSvgPoint(event, preview = false) {
3975
+ const svg = this.svgRef.nativeElement;
3976
+ const pt = svg.createSVGPoint();
3977
+ pt.x = event.clientX;
3978
+ pt.y = event.clientY;
3979
+ const result = pt.matrixTransform(svg.getScreenCTM().inverse());
3980
+ if (preview) {
3981
+ return { x: result.x, y: result.y };
3982
+ }
3983
+ else {
3984
+ return { x: event.clientX, y: event.clientY };
3985
+ }
3986
+ }
3987
+ updateSelection() {
3988
+ if (!this.selectStart || !this.selectEnd)
3989
+ return;
3990
+ const x1 = Math.min(this.selectStart.x, this.selectEnd.x);
3991
+ const y1 = Math.min(this.selectStart.y, this.selectEnd.y);
3992
+ const x2 = Math.max(this.selectStart.x, this.selectEnd.x);
3993
+ const y2 = Math.max(this.selectStart.y, this.selectEnd.y);
3994
+ this.lstSelectedStageCode.clear();
3995
+ for (const n of this.lstStage) {
3996
+ const nx1 = n.x;
3997
+ const ny1 = n.y;
3998
+ const nx2 = n.x + 160;
3999
+ const ny2 = n.y + 80;
4000
+ const intersect = nx2 >= x1 && nx1 <= x2 && ny2 >= y1 && ny1 <= y2;
4001
+ if (intersect) {
4002
+ this.lstSelectedStageCode.add(n.Code);
4003
+ }
4004
+ }
4005
+ }
4006
+ snapPoint(p, event) {
4007
+ if (!this.snapToGrid || event?.altKey)
4008
+ return p;
4009
+ if (!this.snapToGrid)
4010
+ return p;
4011
+ return {
4012
+ x: this.snap(p.x),
4013
+ y: this.snap(p.y),
4014
+ };
4015
+ }
4016
+ snap(value) {
4017
+ return Math.round(value / this.gridSize) * this.gridSize;
4018
+ }
4019
+ getNode(id) {
4020
+ return this.lstStage.find((n) => n.Code === id);
4021
+ }
4022
+ //#endregion
4023
+ //#region BUILD PATH
4024
+ buildPath(edge) {
4025
+ if (!edge.FromStage || !edge.ToStage)
4026
+ return '';
4027
+ const from = this.getNode(edge.FromStage);
4028
+ const to = this.getNode(edge.ToStage);
4029
+ const start = {
4030
+ x: from.x + (from.isReverse ? 0 : 160),
4031
+ y: from.y + 40,
4032
+ };
4033
+ const end = {
4034
+ x: to.x + (to.isReverse ? 160 : 0),
4035
+ y: to.y + 40,
4036
+ };
4037
+ if (edge.points && edge.points.length) {
4038
+ return this.buildPathThroughPoints([start, ...edge.points, end]);
4039
+ }
4040
+ // return this.buildRoutedPath({ node: from, side: edge.fromSide }, to.x, to.y + 40);
4041
+ return this.buildStragePath(start.x, start.y, end.x, end.y);
4042
+ }
4043
+ buildPreviewPath() {
4044
+ if (!this.connectingFrom)
4045
+ return '';
4046
+ const start = {
4047
+ x: this.connectingFrom.node.x + (this.connectingFrom.node.isReverse ? 0 : 160),
4048
+ y: this.connectingFrom.node.y + 40,
4049
+ };
4050
+ const end = {
4051
+ x: this.previewX,
4052
+ y: this.previewY,
4053
+ };
4054
+ // ⭐ ĐÃ CÓ WAYPOINT → MANUAL PREVIEW
4055
+ if (this.connectingPoints.length > 0) {
4056
+ return this.buildPathThroughPoints([start, ...this.connectingPoints, end], 0);
4057
+ }
4058
+ // ⭐ CHƯA CÓ WAYPOINT → AUTO PREVIEW
4059
+ // return this.buildRoutedPath(this.connectingFrom, end.x, end.y, 0);
4060
+ return this.buildStragePath(start.x, start.y, end.x, end.y, 0);
4061
+ }
4062
+ buildStragePath(fromX, fromY, toX, toY, ARROW_GAP = 12) {
4063
+ const x1 = fromX;
4064
+ const y1 = fromY;
4065
+ const x2 = toX;
4066
+ const y2 = toY;
4067
+ /* =============================
4068
+ * 1. VECTOR GỐC
4069
+ * ============================= */
4070
+ const dx = x2 - x1;
4071
+ const dy = y2 - y1;
4072
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
4073
+ /* =============================
4074
+ * 2. SHRINK START & END
4075
+ * ============================= */
4076
+ const sx = x1 + (dx / len) * ARROW_GAP;
4077
+ const sy = y1 + (dy / len) * ARROW_GAP;
4078
+ const ex = x2 - (dx / len) * ARROW_GAP;
4079
+ const ey = y2 - (dy / len) * ARROW_GAP;
4080
+ /* =============================
4081
+ * 3. PATH THẲNG
4082
+ * ============================= */
4083
+ return `
4084
+ M ${sx} ${sy}
4085
+ L ${ex} ${ey}
4086
+ `;
4087
+ }
4088
+ buildRoutedPath(from, toX, toY, ARROW_GAP = 12) {
4089
+ const x1 = from.node.isReverse ? from.node.x : from.node.x + 160;
4090
+ const y1 = from.node.y + 40;
4091
+ const x2 = toX;
4092
+ const y2 = toY;
4093
+ // const ARROW_GAP = 12;
4094
+ /* =============================
4095
+ * 1. VECTOR GỐC
4096
+ * ============================= */
4097
+ const dx = x2 - x1;
4098
+ const dy = y2 - y1;
4099
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
4100
+ const ux = dx / len;
4101
+ const uy = dy / len;
4102
+ /* =============================
4103
+ * 2. START / END (CÁCH CONNECTOR ĐỀU)
4104
+ * ============================= */
4105
+ let sx = x1 + ux * ARROW_GAP;
4106
+ let sy = y1 + uy * ARROW_GAP;
4107
+ let ex = x2 - ux * ARROW_GAP;
4108
+ let ey = y2 - uy * ARROW_GAP;
4109
+ /* =============================
4110
+ * 3. KIỂM TRA BỊ CHẮN KHÔNG
4111
+ * ============================= */
4112
+ const blocked = this.isBlockedPoint(from, x2, y2);
4113
+ /* =============================
4114
+ * 4. ĐỘ CONG CƠ BẢN
4115
+ * ============================= */
4116
+ const dist = Math.sqrt((ex - sx) * (ex - sx) + (ey - sy) * (ey - sy));
4117
+ const BASE_CURVE = Math.max(30, Math.min(80, dist / 2));
4118
+ /* =============================
4119
+ * 5. KHÔNG BỊ CHẮN → CONG TRỰC TIẾP
4120
+ * ============================= */
4121
+ if (!blocked) {
4122
+ let c1x, c1y;
4123
+ let c2x, c2y;
4124
+ if (Math.abs(dy) < Math.abs(dx)) {
4125
+ // thiên ngang
4126
+ c1x = sx + BASE_CURVE;
4127
+ c1y = sy;
4128
+ c2x = ex - BASE_CURVE;
4129
+ c2y = ey;
4130
+ }
4131
+ else {
4132
+ // thiên dọc
4133
+ const dir = ey > sy ? 1 : -1;
4134
+ c1x = sx;
4135
+ c1y = sy + dir * BASE_CURVE;
4136
+ c2x = ex;
4137
+ c2y = ey - dir * BASE_CURVE;
4138
+ }
4139
+ return `
4140
+ M ${sx} ${sy}
4141
+ C ${c1x} ${c1y},
4142
+ ${c2x} ${c2y},
4143
+ ${ex} ${ey}
4144
+ `;
4145
+ }
4146
+ /* =============================
4147
+ * 6. BỊ CHẮN → ROUTE NÉ (S-CURVE)
4148
+ * ============================= */
4149
+ const routeY = this.findBestRouteY(from, ex, ey);
4150
+ const midX = (sx + ex) / 2;
4151
+ const CURVE = 40;
4152
+ return `
4153
+ M ${sx} ${sy}
4154
+ C ${sx + CURVE} ${sy},
4155
+ ${sx + CURVE} ${routeY},
4156
+ ${midX} ${routeY}
4157
+ S ${ex - CURVE} ${routeY},
4158
+ ${ex} ${ey}
4159
+ `;
4160
+ }
4161
+ buildPathThroughPoints(points, ARROW_GAP = 12, tension = 0.1) {
4162
+ if (points.length < 2)
4163
+ return '';
4164
+ const start = this.shrinkPoint(points[0], points[1], ARROW_GAP);
4165
+ const end = this.shrinkPoint(points[points.length - 1], points[points.length - 2], ARROW_GAP);
4166
+ const pts = [start, ...points.slice(1, -1), end];
4167
+ let d = `M ${pts[0].x} ${pts[0].y}`;
4168
+ for (let i = 0; i < pts.length - 1; i++) {
4169
+ const p0 = pts[i - 1] || pts[i];
4170
+ const p1 = pts[i];
4171
+ const p2 = pts[i + 1];
4172
+ const p3 = pts[i + 2] || p2;
4173
+ const c1x = p1.x + (p2.x - p0.x) * tension;
4174
+ const c1y = p1.y + (p2.y - p0.y) * tension;
4175
+ const c2x = p2.x - (p3.x - p1.x) * tension;
4176
+ const c2y = p2.y - (p3.y - p1.y) * tension;
4177
+ d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
4178
+ }
4179
+ return d;
4180
+ }
4181
+ shrinkPoint(from, to, gap) {
4182
+ const dx = to.x - from.x;
4183
+ const dy = to.y - from.y;
4184
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
4185
+ return {
4186
+ x: from.x + (dx / len) * gap,
4187
+ y: from.y + (dy / len) * gap,
4188
+ };
4189
+ }
4190
+ isBlockedPoint(from, x2, y2) {
4191
+ const x1 = from.node.isReverse ? from.node.x + 160 : from.node.x;
4192
+ const y1 = from.node.y + 40;
4193
+ return this.lstStage.some((n) => {
4194
+ if (n.Code === from.node.Code)
4195
+ return false;
4196
+ // node phải nằm giữa theo trục X
4197
+ if (!this.isNodeBetweenX(x1, x2, n))
4198
+ return false;
4199
+ // line phải cắt node theo trục Y
4200
+ return this.doesLineCrossNodeY(y1, n);
4201
+ });
4202
+ }
4203
+ findBestRouteY(from, toX, toY) {
4204
+ const baseY = from.node.y + 40;
4205
+ const OFFSETS = [0, 80, -80, 160, -160, 240, -240];
4206
+ for (const offset of OFFSETS) {
4207
+ const routeY = baseY + offset;
4208
+ if (this.isLaneClear(from, toX, routeY)) {
4209
+ return routeY;
4210
+ }
4211
+ }
4212
+ // fallback: dùng lane xa nhất
4213
+ return baseY + OFFSETS[OFFSETS.length - 1];
4214
+ }
4215
+ isNodeBetweenX(fromX, toX, node) {
4216
+ const nx1 = node.x;
4217
+ const nx2 = node.x + 160;
4218
+ const minX = Math.min(fromX, toX);
4219
+ const maxX = Math.max(fromX, toX);
4220
+ return nx2 > minX && nx1 < maxX;
4221
+ }
4222
+ doesLineCrossNodeY(y, node, tolerance = 6) {
4223
+ const top = node.y;
4224
+ const bottom = node.y + 80;
4225
+ return y > top + tolerance && y < bottom - tolerance;
4226
+ }
4227
+ isLaneClear(from, toX, routeY) {
4228
+ const x1 = from.node.x;
4229
+ const y1 = from.node.y + 40;
4230
+ const x2 = toX;
4231
+ const y2 = routeY;
4232
+ return !this.lstStage.some((n) => {
4233
+ if (n.Code === from.node.Code)
4234
+ return false;
4235
+ // node phải nằm giữa theo trục X
4236
+ if (!this.isNodeBetweenX(x1, x2, n))
4237
+ return false;
4238
+ // lane Y cắt node
4239
+ return this.doesLineCrossNodeY(routeY, n);
4240
+ });
4241
+ }
4242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.7", ngImport: i0, type: WorkflowEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
4243
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.7", type: WorkflowEditorComponent, isStandalone: true, selector: "app-workflow-editor", inputs: { lstOrg: "lstOrg", lstTemplateType: "lstTemplateType", lstTemplatePrint: "lstTemplatePrint", template: "template", lstStage: "lstStage", lstAction: "lstAction" }, outputs: { onSave: "onSave" }, host: { listeners: { "window:keydown": "onKeyDown($event)" } }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true, static: true }, { propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, static: true }, { propertyName: "inputStageStatusElement", first: true, predicate: ["inputStageStatusElement"], descendants: true }, { propertyName: "inputActionTypeElement", first: true, predicate: ["inputActionTypeElement"], descendants: true }, { propertyName: "inputElement", first: true, predicate: ["inputElement"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div\n #canvasRef\n class=\"workflow-canvas\"\n [class.connecting]=\"!!connectingFrom\"\n [class.dragging-point]=\"!!draggingPoint\"\n [style.width.px]=\"CANVAS_WIDTH\"\n [style.height.px]=\"CANVAS_HEIGHT\"\n (mousedown)=\"onMouseDownCanvas($event)\"\n (mousemove)=\"onMouseMoveCanvas($event)\"\n (mouseup)=\"onMouseUpCanvas($event)\"\n (click)=\"onClickCanvas($event)\"\n>\n <!-- \n\n [style.width.px]=\"getCanvasWidth()\"\n [style.height.px]=\"getCanvasHeight()\" \n\n style=\"width: 100%; height: calc(100vh - 120px)\"\n \n -->\n\n <div class=\"toolbar\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <button nz-button nzSize=\"small\" nz-tooltip=\"Editor setting\" (click)=\"settingVisible = true\">\n <nz-icon nzType=\"setting\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button nz-button nzSize=\"small\" nz-tooltip=\"Template setting\" (click)=\"drawTemplateVisibel = true\">\n <nz-icon nzType=\"setting\"></nz-icon> T\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button nz-button nzSize=\"small\" nzDanger nz-tooltip=\"Save\" (click)=\"save()\">\n <nz-icon nzType=\"save\"></nz-icon>\n </button>\n <button nz-button nzSize=\"small\" nz-tooltip=\"Add stage\" (click)=\"addStage()\">\n <nz-icon nzType=\"plus\" class=\"color-primary\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button\n nz-button\n nzSize=\"small\"\n [nzType]=\"isMoveMode ? 'primary' : 'default'\"\n nz-tooltip=\"Move mode\"\n (click)=\"isMoveMode = true\"\n >\n <nz-icon><img src=\"/assets/icon/hand-palm.png\" width=\"19\" height=\"19\" /></nz-icon>\n </button>\n <button\n nz-button\n nzSize=\"small\"\n [nzType]=\"!isMoveMode ? 'primary' : 'default'\"\n nz-tooltip=\"Select mode\"\n (click)=\"isMoveMode = false\"\n >\n <nz-icon><img src=\"/assets/icon/cursor.png\" width=\"19\" height=\"19\" /></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <!-- UNDO / REDO -->\n <button\n nz-button\n nzSize=\"small\"\n nzType=\"default\"\n [disabled]=\"undoStack.length === 0\"\n nz-tooltip=\"Undo\"\n (click)=\"undo()\"\n >\n <nz-icon nzType=\"undo\"></nz-icon>\n </button>\n\n <button\n nz-button\n nzSize=\"small\"\n nzType=\"default\"\n [disabled]=\"redoStack.length === 0\"\n nz-tooltip=\"Redo\"\n (click)=\"redo()\"\n >\n <nz-icon nzType=\"redo\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <!-- ALIGN -->\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignLeft()\">\u27F8</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignCenter()\">\u2261</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignRight()\">\u27F9</button>\n\n <span class=\"divider\"></span>\n\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignTop()\">\u21D1</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignMiddle()\">\u2550</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignBottom()\">\u21D3</button>\n\n <span class=\"divider\"></span>\n\n <!-- DISTRIBUTE -->\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 3\" (click)=\"distributeHorizontal()\">\n \u21C4\n </button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 3\" (click)=\"distributeVertical()\">\n \u21C5\n </button>\n </div>\n\n <div\n class=\"canvas-content\"\n [class.panning]=\"isMoveMode\"\n [style.transform]=\"'translate(' + panX + 'px,' + panY + 'px)'\"\n >\n <!-- GRID -->\n <div *ngIf=\"wfcSetting.ShowGrid\" class=\"grid-layer\"></div>\n\n <!-- <div class=\"zoom-toolbar\">\n <button nz-button nzSize=\"small\" (click)=\"zoomOut()\">\u2212</button>\n <span>{{ zoom * 100 | number: \"1.0-0\" }}%</span>\n <button nz-button nzSize=\"small\" (click)=\"zoomIn()\">+</button>\n </div> -->\n\n <!-- SVG EDGES -->\n <svg #svg class=\"edges-layer\" width=\"100%\" height=\"100%\" preserveAspectRatio=\"none\">\n <g [attr.transform]=\"svgTransform\">\n <!-- DEFS -->\n <defs>\n <!-- glow effect -->\n <filter id=\"edge-glow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"2\" result=\"blur\" />\n <feMerge>\n <feMergeNode in=\"blur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n\n <!-- arrow markers -->\n <marker\n id=\"arrow-blue\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n refX=\"10\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"blue\" d=\"M0,0 L10,5 L0,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-red\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n refX=\"10\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"red\" d=\"M0,0 L10,5 L0,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-blue-start\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n viewBox=\"0 0 10 10\"\n refX=\"0\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"blue\" d=\"M10,0 L0,5 L10,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-red-start\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n viewBox=\"0 0 10 10\"\n refX=\"0\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"red\" d=\"M10,0 L0,5 L10,10\"></path>\n </marker>\n </defs>\n\n <!-- EDGES B\u00CCNH TH\u01AF\u1EDCNG -->\n <ng-container *ngFor=\"let e of normalEdges; let i = index\">\n <path\n class=\"edge-hit\"\n [attr.d]=\"buildPath(e)\"\n (mouseenter)=\"onMouseEnterEdge(e)\"\n (mouseleave)=\"onMouseLeaveEdge(e)\"\n (click)=\"onClickEdge(e, $event)\"\n ></path>\n\n <path\n class=\"edge\"\n [attr.id]=\"'edge-path-' + i\"\n [attr.d]=\"buildPath(e)\"\n marker-end=\"url(#arrow-blue)\"\n [attr.marker-start]=\"e.allowBack ? 'url(#arrow-blue-start)' : ''\"\n ></path>\n\n <!-- LABEL -->\n <!-- text b\u00E1m theo line -->\n <!-- \n <text *ngIf=\"e.ActionText\" class=\"edge-label\" dy=\"-6\">\n <textPath [attr.href]=\"'#edge-path-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.ActionText }}\n </textPath>\n </text> \n \n <text *ngIf=\"e.allowBack && e.labelBack\" class=\"edge-label\" dy=\"14\">\n <textPath [attr.href]=\"'#edge-path-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.labelBack }}\n </textPath>\n </text>\n -->\n\n <!-- text th\u1EB3ng n\u1EB1m ngang -->\n <text\n *ngIf=\"e.ActionText\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y - 8\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.ActionText }}\n </text>\n\n <text\n *ngIf=\"e.allowBack && e.labelBack\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y + 14\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.labelBack }}\n </text>\n </ng-container>\n\n <!-- EDGE \u0110ANG HOVER (LU\u00D4N TR\u00CAN C\u00D9NG) -->\n <ng-container *ngFor=\"let e of hoverEdges; let i = index\">\n <path\n class=\"edge-hit\"\n [attr.d]=\"buildPath(e)\"\n (mouseleave)=\"onMouseLeaveEdge(e)\"\n (click)=\"onClickEdge(e, $event)\"\n ></path>\n\n <path\n class=\"edge red\"\n [attr.id]=\"'edge-path-hover-' + i\"\n [attr.d]=\"buildPath(e)\"\n marker-end=\"url(#arrow-red)\"\n [attr.marker-start]=\"e.allowBack ? 'url(#arrow-red-start)' : ''\"\n ></path>\n\n <!-- LABEL -->\n <!-- <text *ngIf=\"e.ActionText\" class=\"edge-label\" dy=\"-6\">\n <textPath [attr.href]=\"'#edge-path-hover-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.ActionText }}\n </textPath>\n </text>\n\n <text *ngIf=\"e.allowBack && e.labelBack\" class=\"edge-label\" dy=\"14\">\n <textPath [attr.href]=\"'#edge-path-hover-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.labelBack }}\n </textPath>\n </text> -->\n\n <text\n *ngIf=\"e.ActionText\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y - 8\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.ActionText }}\n </text>\n\n <text\n *ngIf=\"e.allowBack && e.labelBack\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y + 14\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.labelBack }}\n </text>\n </ng-container>\n\n <!-- preview -->\n <path\n *ngIf=\"connectingFrom\"\n class=\"edge\"\n [attr.d]=\"buildPreviewPath()\"\n stroke-dasharray=\"5,5\"\n marker-end=\"url(#arrow-blue)\"\n ></path>\n </g>\n </svg>\n\n <div\n *ngIf=\"isSelecting && selectStart && selectEnd\"\n class=\"selection-box\"\n [style.left.px]=\"Math.min(selectStart.x, selectEnd.x)\"\n [style.top.px]=\"Math.min(selectStart.y, selectEnd.y)\"\n [style.width.px]=\"Math.abs(selectEnd.x - selectStart.x)\"\n [style.height.px]=\"Math.abs(selectEnd.y - selectStart.y)\"\n ></div>\n\n <!-- lstStage -->\n @for (n of lstStage; track $index) {\n @if (n.StageType == \"NODE\") {\n <nz-card\n #nodeEl\n class=\"workflow-node\"\n [attr.data-id]=\"n.Code\"\n [class.selected]=\"n == selectedStage || n == hoverStage || lstSelectedStageCode.has(n.Code)\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n [class.connecting-source]=\"isConnectingFrom(n) || isDraggingFrom(n) || isSelectedNode(n)\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">{{ n.Code }}</div>\n\n <div>{{ n.Name }}</div>\n\n @if (!n.isReverse) {\n <!-- connector RIGHT: output -->\n <div class=\"connector connector-right\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n\n <!-- connector LEFT: input -->\n <div class=\"connector connector-left\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n } @else {\n <div class=\"connector connector-right reverse\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n <div class=\"connector connector-left reverse\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n }\n </nz-card>\n }\n <!-- START NODE -->\n @else if (n.StageType === \"START\") {\n <nz-card\n class=\"workflow-node workflow-node-start\"\n style=\"border: unset\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">&nbsp;</div>\n\n <nz-tag\n [class.selected]=\"n == selectedStage || lstSelectedStageCode.has(n.Code)\"\n style=\"position: relative; top: -13px\"\n [style.right.px]=\"n.isReverseLabel ? -83 : 1\"\n [nzColor]=\"'green'\"\n >\n {{ n.StageType }}\n </nz-tag>\n\n <!-- connector RIGHT: output -->\n <div class=\"connector connector-right connector-start\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n </nz-card>\n }\n\n <!-- END NODE -->\n @else if (n.StageType === \"END\") {\n <nz-card\n class=\"workflow-node workflow-node-end\"\n style=\"border: unset\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n [class.connecting-source]=\"isConnectingFrom(n)\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">&nbsp;</div>\n\n <nz-tag\n [class.selected]=\"n == selectedStage || lstSelectedStageCode.has(n.Code)\"\n style=\"position: relative; top: -13px\"\n [style.right.px]=\"n.isReverseLabel ? 65 : -5\"\n [nzColor]=\"'red'\"\n >\n {{ n.StageType }}\n </nz-tag>\n\n <!-- connector LEFT: input -->\n <div class=\"connector connector-end\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n </nz-card>\n }\n }\n\n <!-- POINTS -->\n @for (p of connectingPoints; track $index) {\n @if (p != draggingPoint) {\n <div\n class=\"waypoint\"\n [style.left.px]=\"p.x\"\n [style.top.px]=\"p.y\"\n (mousedown)=\"onmousedownPoint($event, p)\"\n (contextmenu)=\"onRightClickPoint($event, hoverEdge!, $index)\"\n ></div>\n } @else {\n <div\n class=\"waypoint dragging\"\n [style.left.px]=\"p.x\"\n [style.top.px]=\"p.y\"\n (mousedown)=\"onmousedownPoint($event, p)\"\n ></div>\n }\n }\n </div>\n</div>\n\n<!-- TEMPLATE -->\n<nz-drawer\n [nzTitle]=\"drawTemplateTitle\"\n nzPlacement=\"right\"\n [nzWidth]=\"500\"\n [nzClosable]=\"false\"\n [nzVisible]=\"drawTemplateVisibel\"\n [nzFooter]=\"footerTplTemplate\"\n (nzOnClose)=\"drawTemplateVisibel = false\"\n>\n <ng-template #drawTemplateTitle> <nz-icon nzType=\"setting\"></nz-icon> &nbsp; Template </ng-template>\n <ng-container *nzDrawerContent>\n <extend-input [label]=\"'Code'\" [(_ngModel)]=\"template.Code\"></extend-input>\n <extend-input [label]=\"'Name'\" [(_ngModel)]=\"template.Name\"></extend-input>\n <extend-select\n [label]=\"'Type'\"\n [lstItem]=\"lstTemplateType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"template.DocType\"\n ></extend-select>\n <extend-textarea [label]=\"'Desscription'\" [(_ngModel)]=\"template.Description\"></extend-textarea>\n <extend-select\n [label]=\"'Print template'\"\n [lstItem]=\"lstTemplatePrint\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"template.TemplatePrint\"\n ></extend-select>\n <extend-checkbox [label]=\"'Active'\" [(_ngModel)]=\"template.IsActive\"></extend-checkbox>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Stage status</h3>\n\n @for (tag of template.lstStageStatus; track tag) {\n <nz-tag\n [nzMode]=\"checkRemoveableStageStatus(tag) ? 'closeable' : 'default'\"\n (nzOnClose)=\"handleCloseStageStatus(tag)\"\n >\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstStageStatus && template.lstStageStatus.length\" />\n\n @if (!inputStageStatusVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInputStageStatus()\">\n <nz-icon nzType=\"plus\" />\n New Action type\n </nz-tag>\n } @else {\n <input\n #inputStageStatusElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Code\"\n [(ngModel)]=\"inputStageStatusCode\"\n (keydown.enter)=\"inputStageStatusNameElement.select()\"\n />\n <input\n #inputStageStatusNameElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Name\"\n [(ngModel)]=\"inputStageStatusName\"\n (blur)=\"handleInputStageStatusConfirm()\"\n (keydown.enter)=\"handleInputStageStatusConfirm()\"\n />\n }\n\n <box [height]=\"16\"></box>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Action type</h3>\n\n @for (tag of template.lstActionType; track tag) {\n <nz-tag\n [nzMode]=\"checkRemoveableActionType(tag) ? 'closeable' : 'default'\"\n (nzOnClose)=\"handleCloseActionType(tag)\"\n >\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstActionType && template.lstActionType.length\" />\n\n @if (!inputActionTypeVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInputActionType()\">\n <nz-icon nzType=\"plus\" />\n New Action type\n </nz-tag>\n } @else {\n <input\n #inputActionTypeElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Code\"\n [(ngModel)]=\"inputActionTypeCode\"\n (keydown.enter)=\"inputActionTypeNameElement.select()\"\n />\n <input\n #inputActionTypeNameElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Name\"\n [(ngModel)]=\"inputActionTypeName\"\n (blur)=\"handleInputActionTypeConfirm()\"\n (keydown.enter)=\"handleInputActionTypeConfirm()\"\n />\n }\n\n <box [height]=\"16\"></box>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Action status</h3>\n\n @for (tag of template.lstActionStatus; track tag) {\n <nz-tag [nzMode]=\"checkRemoveableActionStatus(tag) ? 'closeable' : 'default'\" (nzOnClose)=\"handleClose(tag)\">\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstActionStatus && template.lstActionStatus.length\" />\n\n @if (!inputVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInput()\">\n <nz-icon nzType=\"plus\" />\n New Action status\n </nz-tag>\n } @else {\n <input\n #inputElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"+ New Action status\"\n [(ngModel)]=\"inputValue\"\n (blur)=\"handleInputConfirm()\"\n (keydown.enter)=\"handleInputConfirm()\"\n />\n }\n </ng-container>\n\n <ng-template #footerTplTemplate>\n <div nz-flex [nzGap]=\"6\">\n <button nz-button (click)=\"drawTemplateVisibel = false\">Close</button>\n <button nz-button nzDanger (click)=\"save()\">Save</button>\n </div>\n </ng-template>\n</nz-drawer>\n\n<!-- STAGE -->\n<nz-drawer\n [nzTitle]=\"drawerTitleStage\"\n [nzPlacement]=\"selectedStage?.drawerPosition ? selectedStage?.drawerPosition : 'right'\"\n [nzVisible]=\"!!selectedStage\"\n [nzWidth]=\"750\"\n [nzClosable]=\"false\"\n [nzFooter]=\"footerTplNode\"\n (nzOnClose)=\"selectedStage = undefined\"\n>\n <ng-template #drawerTitleStage>\n <div nz-row nzJustify=\"space-between\">\n @if (selectedStage) {\n @if (selectedStage.drawerPosition == \"left\") {\n <button nz-button nzSize=\"small\" nz-tooltip=\"To the right\" (click)=\"selectedStage.drawerPosition = 'right'\">\n <nz-icon nzType=\"double-right\"></nz-icon>\n </button>\n STAGE\n } @else {\n STAGE\n <button nz-button nzSize=\"small\" nz-tooltip=\"To the left\" (click)=\"selectedStage.drawerPosition = 'left'\">\n <nz-icon nzType=\"double-left\"></nz-icon>\n </button>\n }\n }\n </div>\n </ng-template>\n <ng-container *nzDrawerContent>\n <ng-container *ngIf=\"selectedStage\">\n <nz-radio-group [(ngModel)]=\"selectedStage.StageType\">\n <label nz-radio nzValue=\"START\" [nzDisabled]=\"true\">START</label>\n <label nz-radio nzValue=\"NODE\" [nzDisabled]=\"true\">NODE</label>\n <label nz-radio nzValue=\"END\" [nzDisabled]=\"true\">END</label>\n </nz-radio-group>\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"32\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Code'\"\n [layOutType]=\"'vertical'\"\n [disabled]=\"['START', 'END'].indexOf(selectedStage!.StageType) >= 0\"\n [required]=\"true\"\n [(_ngModel)]=\"selectedStage.Code\"\n ></extend-input>\n\n <extend-textarea\n nz-col\n [nzSpan]=\"16\"\n [label]=\"'Name'\"\n [layOutType]=\"'vertical'\"\n [autoSize]=\"true\"\n [disabled]=\"['START', 'END'].indexOf(selectedStage!.StageType) >= 0\"\n [required]=\"true\"\n [(_ngModel)]=\"selectedStage.Name\"\n ></extend-textarea>\n </div>\n\n <div nz-row [nzGutter]=\"32\">\n <extend-input-number\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Sequence'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedStage.SeqValue\"\n ></extend-input-number>\n\n <extend-select\n nz-col\n [nzSpan]=\"16\"\n [label]=\"'Org implement'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstOrg\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedStage.App_Org_Id\"\n ></extend-select>\n </div>\n\n @if ([\"START\", \"END\"].indexOf(selectedStage.StageType) < 0) {\n <extend-checkbox [label]=\"'Reverse'\" [labelSpan]=\"0\" [(_ngModel)]=\"selectedStage.isReverse\"></extend-checkbox>\n\n <extend-checkbox\n [label]=\"'Require user action'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedStage.IsRequireUser\"\n ></extend-checkbox>\n } @else {\n <extend-checkbox\n [label]=\"'Reverse'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedStage.isReverseLabel\"\n ></extend-checkbox>\n }\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"16\">\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;List Action</h3>\n <div nz-col>\n <button nz-button nzType=\"primary\" nzSize=\"small\" (click)=\"addAction()\">\n <nz-icon nzType=\"plus\"></nz-icon>\n </button>\n </div>\n </div>\n\n <nz-table\n nzSize=\"small\"\n [nzData]=\"selectedStage.lstAction || []\"\n [nzShowPagination]=\"false\"\n [nzNoResult]=\"'&nbsp;'\"\n >\n <thead>\n <tr [hidden]=\"true\">\n <th nzWidth=\"50px\"></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (action of selectedStage.lstAction; track $index) {\n <tr>\n <td>\n <nz-icon\n nzType=\"delete\"\n nzTheme=\"outline\"\n class=\"color-warn cursor-pointer icon-size-16\"\n (click)=\"$event.stopPropagation(); deleteAction(action)\"\n ></nz-icon>\n </td>\n <td (click)=\"hoverEdge = action\">\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"action.ActionText\"\n (_ngModelChange)=\"onchangeActionText(action)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(action)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.NextStageStatus\"\n ></extend-select>\n\n <nz-divider\n *ngIf=\"selectedStage.lstAction && $index + 1 < selectedStage.lstAction.length\"\n style=\"margin: 16px 0px 8px 0px; background-color: orange\"\n ></nz-divider>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </nz-table>\n\n <box [height]=\"16\"></box>\n </ng-container>\n </ng-container>\n\n <ng-template #footerTplNode>\n <div nz-flex [nzGap]=\"6\" [nzJustify]=\"selectedStage && selectedStage.drawerPosition == 'left' ? 'end' : 'start'\">\n <button nz-button (click)=\"selectedStage = undefined\">Close</button>\n <button\n *ngIf=\"selectedStage && ['START', 'END'].indexOf(selectedStage.StageType) < 0\"\n nz-button\n nzDanger\n (click)=\"deleteNode()\"\n >\n Delete\n </button>\n </div>\n </ng-template>\n</nz-drawer>\n\n<!-- EDGE -->\n<nz-drawer\n nzTitle=\"ACTION\"\n nzPlacement=\"right\"\n [nzClosable]=\"false\"\n [nzWidth]=\"750\"\n [nzVisible]=\"!!selectedAction\"\n (nzOnClose)=\"selectedAction = undefined\"\n>\n <ng-container *nzDrawerContent>\n <ng-container *ngIf=\"selectedAction\">\n <strong>{{ selectedAction.FromStage }} -> {{ selectedAction.ToStage }}</strong>\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedAction.ActionText\"\n (_ngModelChange)=\"onchangeActionText(selectedAction)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(selectedAction)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.NextStageStatus\"\n ></extend-select>\n </div>\n\n <!-- <box [height]=\"16\"></box> -->\n <nz-divider></nz-divider>\n\n <extend-checkbox\n [label]=\"'Allow back'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedAction.allowBack\"\n (_ngModelChange)=\"onchangeAllowBack(selectedAction)\"\n ></extend-checkbox>\n\n <box [height]=\"16\"></box>\n\n @if (selectedAction.allowBack) {\n <strong>{{ selectedAction.ToStage }} -> {{ selectedAction.FromStage }}</strong>\n\n @if (selectedBackAction) {\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedBackAction.ActionText\"\n (_ngModelChange)=\"onchangeActionText(selectedBackAction)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(selectedBackAction)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.NextStageStatus\"\n ></extend-select>\n </div>\n }\n }\n </ng-container>\n </ng-container>\n</nz-drawer>\n\n<!-- setting editor -->\n<nz-drawer\n [nzTitle]=\"drawSettingTitle\"\n nzPlacement=\"right\"\n [nzClosable]=\"false\"\n [nzVisible]=\"settingVisible\"\n (nzOnClose)=\"settingVisible = false\"\n>\n <ng-template #drawSettingTitle> <nz-icon nzType=\"setting\"></nz-icon> &nbsp; Editor </ng-template>\n <ng-container *nzDrawerContent>\n <div class=\"form-item-no-bottom\">\n <extend-checkbox [label]=\"'Show grid'\" [labelSpan]=\"0\" [(_ngModel)]=\"wfcSetting.ShowGrid\"></extend-checkbox>\n\n <box [height]=\"16\"></box>\n\n <extend-input-number [label]=\"'Canvas width'\" [(_ngModel)]=\"CANVAS_WIDTH\"></extend-input-number>\n <box [height]=\"16\"></box>\n <extend-input-number [label]=\"'Canvas height'\" [(_ngModel)]=\"CANVAS_HEIGHT\"></extend-input-number>\n </div>\n </ng-container>\n</nz-drawer>\n", styles: ["@charset \"UTF-8\";.workflow-canvas{position:relative;width:100%;height:600px;background:#f0f2f5;cursor:default;overflow:hidden;outline:1px dashed rgba(0,0,0,.1)}.workflow-canvas .edges-layer{position:absolute;inset:0;width:100%;height:100%;z-index:1}.workflow-canvas .edges-layer path{stroke-width:2;fill:none}.canvas-content{transform-origin:0 0;width:10000px;height:10000px}.canvas-content{outline:1px dashed red}.canvas-content.panning{cursor:grab}.canvas-content.panning:active{cursor:grabbing}.workflow-canvas.panning{cursor:grab}.workflow-canvas.panning:active{cursor:grabbing}.workflow-canvas.connecting,.workflow-canvas.connecting .workflow-node{cursor:crosshair}.workflow-node{position:absolute;width:160px;cursor:grab;-webkit-user-select:none;user-select:none;z-index:2}.workflow-node:active{cursor:grabbing}.workflow-node .title{font-weight:600;margin-bottom:6px}.workflow-node.connecting-source,.workflow-node.selected,.workflow-node:hover:not(.workflow-node-start,.workflow-node-end){outline:2px dashed #1890ff}.workflow-node-start{background-color:unset;display:flex;justify-content:right}.workflow-node-end{background-color:unset;display:flex}.connector{position:absolute;top:39px;width:12px;height:12px;background:#1890ff;border-radius:50%;transform:translateY(-50%);cursor:crosshair;transition:box-shadow .15s ease-out,transform .15s ease-out}.connector:hover{box-shadow:0 0 0 3px #1890ff4d;transform:translateY(-50%) scale(1.15)}.connector-right{right:-8px;background:orange}.connector-right.reverse,.connector-left{left:-8px}.connector-left.reverse{left:unset;right:-8px}.connector-start{width:16px;height:16px;right:-3px;background:green;box-shadow:0 0 0 3px #1890ff4d}.connector-end{width:16px;height:16px;left:-6px;background:#000;box-shadow:0 0 0 3px #1890ff4d}.edge{cursor:pointer;stroke:#1890ff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2;fill:none;transition:stroke .15s ease}.edge-hit{stroke:transparent;stroke-width:18px!important;fill:none;cursor:pointer}.edge-hit:hover+.edge{stroke:#ff4d4f;stroke-width:2}.workflow-canvas.dragging-point .edge-hit{pointer-events:none}.edge:hover+.edge-label{fill:#1890ff;font-weight:500}.edge-label{font-size:12px;fill:#555;pointer-events:none;-webkit-user-select:none;user-select:none}.blue{stroke:#1890ff!important}.red{stroke:#ff4d4f!important;box-shadow:0 0 0 3px #1890ff4d!important}.workflow-svg{background:transparent;overflow:visible}.waypoint{position:absolute;width:10px;height:10px;background:#ff4d4f;border-radius:50%;transform:translate(-50%,-50%) scale(1);cursor:move;z-index:1;transition:transform .12s ease-out,background-color .12s ease-out,box-shadow .12s ease-out}.waypoint:hover{background:#ff7875;transform:translate(-50%,-50%) scale(1.25);box-shadow:0 0 0 4px #ff787559}.waypoint.dragging{transition:none;transform:translate(-50%,-50%) scale(1.15);pointer-events:none}.workflow-canvas.dragging-point svg{cursor:grabbing}.selection-box{position:absolute;border:1px dashed #1890ff;background:#1890ff1a;pointer-events:none}.toolbar{position:absolute;top:8px;left:8px;display:flex;gap:4px;padding:6px;background:#fff;border-radius:6px;box-shadow:0 2px 8px #00000026;z-index:100}.toolbar .divider{width:1px;background:#e5e5e5;margin:0 2px}.zoom-toolbar{position:absolute;top:8px;left:8px;z-index:10;display:flex;align-items:center;gap:6px;background:#fff;padding:4px 6px;border-radius:6px;box-shadow:0 2px 6px #00000026}.zoom-toolbar span{min-width:40px;text-align:center;font-size:12px}nz-tag.selected{outline:2px dashed #1890ff}.grid-layer{position:absolute;inset:0;pointer-events:none;z-index:0;background-image:radial-gradient(rgba(0,0,0,.08) 1px,transparent 1px);background-size:20px 20px}.grid-layer{background-image:linear-gradient(to right,rgba(0,0,0,.06) 1px,transparent 1px),linear-gradient(to bottom,rgba(0,0,0,.06) 1px,transparent 1px);background-size:20px 20px}.editable-tag{background:#fff;border-style:dashed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzCardModule }, { kind: "component", type: i3$1.NzCardComponent, selector: "nz-card", inputs: ["nzBordered", "nzLoading", "nzHoverable", "nzBodyStyle", "nzCover", "nzActions", "nzType", "nzSize", "nzTitle", "nzExtra"], exportAs: ["nzCard"] }, { kind: "ngmodule", type: NzDrawerModule }, { kind: "component", type: i4$1.NzDrawerComponent, selector: "nz-drawer", inputs: ["nzContent", "nzCloseIcon", "nzClosable", "nzMaskClosable", "nzMask", "nzCloseOnNavigation", "nzNoAnimation", "nzKeyboard", "nzTitle", "nzExtra", "nzFooter", "nzPlacement", "nzSize", "nzMaskStyle", "nzBodyStyle", "nzWrapClassName", "nzWidth", "nzHeight", "nzZIndex", "nzOffsetX", "nzOffsetY", "nzVisible"], outputs: ["nzOnViewInit", "nzOnClose", "nzVisibleChange"], exportAs: ["nzDrawer"] }, { kind: "directive", type: i4$1.NzDrawerContentDirective, selector: "[nzDrawerContent]", exportAs: ["nzDrawerContent"] }, { kind: "ngmodule", type: NzTagModule }, { kind: "component", type: i5$2.NzTagComponent, selector: "nz-tag", inputs: ["nzMode", "nzColor", "nzChecked", "nzBordered"], outputs: ["nzOnClose", "nzCheckedChange"], exportAs: ["nzTag"] }, { kind: "ngmodule", type: NzRadioModule }, { kind: "component", type: i6$3.NzRadioComponent, selector: "[nz-radio],[nz-radio-button]", inputs: ["nzValue", "nzDisabled", "nzAutoFocus", "nz-radio-button"], exportAs: ["nzRadio"] }, { kind: "component", type: i6$3.NzRadioGroupComponent, selector: "nz-radio-group", inputs: ["nzDisabled", "nzButtonStyle", "nzSize", "nzName"], exportAs: ["nzRadioGroup"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i2.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "component", type: Box, selector: "box", inputs: ["display", "width", "height"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i8.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i9.ɵNzTransitionPatchDirective, selector: "[nz-button], nz-button-group, [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i10.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzToolTipModule }, { kind: "directive", type: i11.NzTooltipDirective, selector: "[nz-tooltip]", inputs: ["nzTooltipTitle", "nzTooltipTitleContext", "nz-tooltip", "nzTooltipTrigger", "nzTooltipPlacement", "nzTooltipOrigin", "nzTooltipVisible", "nzTooltipMouseEnterDelay", "nzTooltipMouseLeaveDelay", "nzTooltipOverlayClassName", "nzTooltipOverlayStyle", "nzTooltipArrowPointAtCenter", "cdkConnectedOverlayPush", "nzTooltipColor"], outputs: ["nzTooltipVisibleChange"], exportAs: ["nzTooltip"] }, { kind: "component", type: ExtendInput, selector: "extend-input", inputs: ["layOutType", "label", "placeHolder", "labelAlign", "inputClass", "labelSpan", "allowClear", "disabled", "readOnly", "required", "noBottom", "selectModeType", "autocomplete", "inputWidth", "inputHeight", "borderBottomOnly", "displayInline", "size", "lstItem", "displayField", "valueField", "formData", "controlName", "_ngModel"], outputs: ["_ngModelChange", "onclickClearIcon", "onenter"] }, { kind: "component", type: ExtendSelectComponent, selector: "extend-select", inputs: ["layOutType", "label", "placeHolder", "labelAlign", "labelSpan", "disabled", "required", "noBottom", "multiple", "showSelectAll", "maxTagCount", "inputWidth", "inputHeight", "borderBottomOnly", "displayInline", "size", "lstItem", "displayField", "displayFields", "valueField", "formData", "controlName", "_ngModel"], outputs: ["_ngModelChange", "itemChange", "onFocus"] }, { kind: "component", type: ExtendCheckbox, selector: "extend-checkbox", inputs: ["label", "labelSpan", "disabled", "formData", "controlName", "valueType", "_ngModel"], outputs: ["_ngModelChange"] }, { kind: "component", type: ExtendTextArea, selector: "extend-textarea", inputs: ["layOutType", "label", "placeHolder", "labelAlign", "labelSpan", "inputSpan", "disabled", "required", "noBottom", "selectModeType", "inputClass", "minRows", "maxRows", "autoSize", "lstItem", "displayField", "valueField", "formData", "controlName", "_ngModel"], outputs: ["_ngModelChange"] }, { kind: "ngmodule", type: NzInputModule }, { kind: "directive", type: i6$1.NzInputDirective, selector: "input[nz-input],textarea[nz-input]", inputs: ["nzBorderless", "nzSize", "nzStepperless", "nzStatus", "disabled"], exportAs: ["nzInput"] }, { kind: "ngmodule", type: NzGridModule }, { kind: "directive", type: i4.NzColDirective, selector: "[nz-col],nz-col,nz-form-control,nz-form-label", inputs: ["nzFlex", "nzSpan", "nzOrder", "nzOffset", "nzPush", "nzPull", "nzXs", "nzSm", "nzMd", "nzLg", "nzXl", "nzXXl"], exportAs: ["nzCol"] }, { kind: "directive", type: i4.NzRowDirective, selector: "[nz-row],nz-row,nz-form-item", inputs: ["nzAlign", "nzJustify", "nzGutter"], exportAs: ["nzRow"] }, { kind: "ngmodule", type: NzFlexModule }, { kind: "directive", type: i14.NzFlexDirective, selector: "[nz-flex],nz-flex", inputs: ["nzVertical", "nzJustify", "nzAlign", "nzGap", "nzWrap", "nzFlex"], exportAs: ["nzFlex"] }, { kind: "component", type: ExtendInputNumber, selector: "extend-input-number", inputs: ["layOutType", "label", "placeHolder", "labelAlign", "labelSpan", "disabled", "required", "noBottom", "size", "min", "max", "precision", "inputWidth", "inputHeight", "borderBottomOnly", "displayInline", "separatorType", "formData", "controlName", "_ngModel"], outputs: ["_ngModelChange"] }, { kind: "ngmodule", type: NzTableModule }, { kind: "component", type: i15.NzTableComponent, selector: "nz-table", inputs: ["nzTableLayout", "nzShowTotal", "nzItemRender", "nzTitle", "nzFooter", "nzNoResult", "nzPageSizeOptions", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualForTrackBy", "nzLoadingDelay", "nzPageIndex", "nzPageSize", "nzTotal", "nzWidthConfig", "nzData", "nzCustomColumn", "nzPaginationPosition", "nzScroll", "noDataVirtualHeight", "nzPaginationType", "nzFrontPagination", "nzTemplateMode", "nzShowPagination", "nzLoading", "nzOuterBordered", "nzLoadingIndicator", "nzBordered", "nzSize", "nzShowSizeChanger", "nzHideOnSinglePage", "nzShowQuickJumper", "nzSimple"], outputs: ["nzPageSizeChange", "nzPageIndexChange", "nzQueryParams", "nzCurrentPageDataChange", "nzCustomColumnChange"], exportAs: ["nzTable"] }, { kind: "directive", type: i15.NzTableCellDirective, selector: "th:not(.nz-disable-th):not([mat-cell]), td:not(.nz-disable-td):not([mat-cell])" }, { kind: "directive", type: i15.NzThMeasureDirective, selector: "th", inputs: ["nzWidth", "colspan", "colSpan", "rowspan", "rowSpan"] }, { kind: "component", type: i15.NzTheadComponent, selector: "thead:not(.ant-table-thead)", outputs: ["nzSortOrderChange"] }, { kind: "component", type: i15.NzTbodyComponent, selector: "tbody" }, { kind: "directive", type: i15.NzTrDirective, selector: "tr:not([mat-row]):not([mat-header-row]):not([nz-table-measure-row]):not([nzExpand]):not([nz-table-fixed-row])" }, { kind: "ngmodule", type: NzDividerModule }, { kind: "component", type: i16.NzDividerComponent, selector: "nz-divider", inputs: ["nzText", "nzType", "nzOrientation", "nzVariant", "nzDashed", "nzPlain"], exportAs: ["nzDivider"] }] });
4244
+ }
4245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.7", ngImport: i0, type: WorkflowEditorComponent, decorators: [{
4246
+ type: Component,
4247
+ args: [{ selector: 'app-workflow-editor', imports: [
4248
+ CommonModule,
4249
+ FormsModule,
4250
+ NzCardModule,
4251
+ NzDrawerModule,
4252
+ NzTagModule,
4253
+ NzRadioModule,
4254
+ NzIconModule,
4255
+ Box,
4256
+ NzButtonModule,
4257
+ NzToolTipModule,
4258
+ ExtendInput,
4259
+ ExtendSelectComponent,
4260
+ ExtendCheckbox,
4261
+ ExtendTextArea,
4262
+ NzInputModule,
4263
+ NzGridModule,
4264
+ NzFlexModule,
4265
+ ExtendInputNumber,
4266
+ NzTableModule,
4267
+ NzDividerModule,
4268
+ ], template: "<div\n #canvasRef\n class=\"workflow-canvas\"\n [class.connecting]=\"!!connectingFrom\"\n [class.dragging-point]=\"!!draggingPoint\"\n [style.width.px]=\"CANVAS_WIDTH\"\n [style.height.px]=\"CANVAS_HEIGHT\"\n (mousedown)=\"onMouseDownCanvas($event)\"\n (mousemove)=\"onMouseMoveCanvas($event)\"\n (mouseup)=\"onMouseUpCanvas($event)\"\n (click)=\"onClickCanvas($event)\"\n>\n <!-- \n\n [style.width.px]=\"getCanvasWidth()\"\n [style.height.px]=\"getCanvasHeight()\" \n\n style=\"width: 100%; height: calc(100vh - 120px)\"\n \n -->\n\n <div class=\"toolbar\" (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\n <button nz-button nzSize=\"small\" nz-tooltip=\"Editor setting\" (click)=\"settingVisible = true\">\n <nz-icon nzType=\"setting\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button nz-button nzSize=\"small\" nz-tooltip=\"Template setting\" (click)=\"drawTemplateVisibel = true\">\n <nz-icon nzType=\"setting\"></nz-icon> T\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button nz-button nzSize=\"small\" nzDanger nz-tooltip=\"Save\" (click)=\"save()\">\n <nz-icon nzType=\"save\"></nz-icon>\n </button>\n <button nz-button nzSize=\"small\" nz-tooltip=\"Add stage\" (click)=\"addStage()\">\n <nz-icon nzType=\"plus\" class=\"color-primary\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <button\n nz-button\n nzSize=\"small\"\n [nzType]=\"isMoveMode ? 'primary' : 'default'\"\n nz-tooltip=\"Move mode\"\n (click)=\"isMoveMode = true\"\n >\n <nz-icon><img src=\"/assets/icon/hand-palm.png\" width=\"19\" height=\"19\" /></nz-icon>\n </button>\n <button\n nz-button\n nzSize=\"small\"\n [nzType]=\"!isMoveMode ? 'primary' : 'default'\"\n nz-tooltip=\"Select mode\"\n (click)=\"isMoveMode = false\"\n >\n <nz-icon><img src=\"/assets/icon/cursor.png\" width=\"19\" height=\"19\" /></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <!-- UNDO / REDO -->\n <button\n nz-button\n nzSize=\"small\"\n nzType=\"default\"\n [disabled]=\"undoStack.length === 0\"\n nz-tooltip=\"Undo\"\n (click)=\"undo()\"\n >\n <nz-icon nzType=\"undo\"></nz-icon>\n </button>\n\n <button\n nz-button\n nzSize=\"small\"\n nzType=\"default\"\n [disabled]=\"redoStack.length === 0\"\n nz-tooltip=\"Redo\"\n (click)=\"redo()\"\n >\n <nz-icon nzType=\"redo\"></nz-icon>\n </button>\n\n <box [width]=\"1\"></box>\n <span class=\"divider\"></span>\n <box [width]=\"1\"></box>\n\n <!-- ALIGN -->\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignLeft()\">\u27F8</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignCenter()\">\u2261</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignRight()\">\u27F9</button>\n\n <span class=\"divider\"></span>\n\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignTop()\">\u21D1</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignMiddle()\">\u2550</button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 2\" (click)=\"alignBottom()\">\u21D3</button>\n\n <span class=\"divider\"></span>\n\n <!-- DISTRIBUTE -->\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 3\" (click)=\"distributeHorizontal()\">\n \u21C4\n </button>\n <button nz-button nzSize=\"small\" [disabled]=\"lstSelectedStageCode.size < 3\" (click)=\"distributeVertical()\">\n \u21C5\n </button>\n </div>\n\n <div\n class=\"canvas-content\"\n [class.panning]=\"isMoveMode\"\n [style.transform]=\"'translate(' + panX + 'px,' + panY + 'px)'\"\n >\n <!-- GRID -->\n <div *ngIf=\"wfcSetting.ShowGrid\" class=\"grid-layer\"></div>\n\n <!-- <div class=\"zoom-toolbar\">\n <button nz-button nzSize=\"small\" (click)=\"zoomOut()\">\u2212</button>\n <span>{{ zoom * 100 | number: \"1.0-0\" }}%</span>\n <button nz-button nzSize=\"small\" (click)=\"zoomIn()\">+</button>\n </div> -->\n\n <!-- SVG EDGES -->\n <svg #svg class=\"edges-layer\" width=\"100%\" height=\"100%\" preserveAspectRatio=\"none\">\n <g [attr.transform]=\"svgTransform\">\n <!-- DEFS -->\n <defs>\n <!-- glow effect -->\n <filter id=\"edge-glow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <feGaussianBlur stdDeviation=\"2\" result=\"blur\" />\n <feMerge>\n <feMergeNode in=\"blur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n\n <!-- arrow markers -->\n <marker\n id=\"arrow-blue\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n refX=\"10\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"blue\" d=\"M0,0 L10,5 L0,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-red\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n refX=\"10\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"red\" d=\"M0,0 L10,5 L0,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-blue-start\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n viewBox=\"0 0 10 10\"\n refX=\"0\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"blue\" d=\"M10,0 L0,5 L10,10\"></path>\n </marker>\n\n <marker\n id=\"arrow-red-start\"\n markerWidth=\"10\"\n markerHeight=\"10\"\n viewBox=\"0 0 10 10\"\n refX=\"0\"\n refY=\"5\"\n orient=\"auto\"\n markerUnits=\"strokeWidth\"\n >\n <path class=\"red\" d=\"M10,0 L0,5 L10,10\"></path>\n </marker>\n </defs>\n\n <!-- EDGES B\u00CCNH TH\u01AF\u1EDCNG -->\n <ng-container *ngFor=\"let e of normalEdges; let i = index\">\n <path\n class=\"edge-hit\"\n [attr.d]=\"buildPath(e)\"\n (mouseenter)=\"onMouseEnterEdge(e)\"\n (mouseleave)=\"onMouseLeaveEdge(e)\"\n (click)=\"onClickEdge(e, $event)\"\n ></path>\n\n <path\n class=\"edge\"\n [attr.id]=\"'edge-path-' + i\"\n [attr.d]=\"buildPath(e)\"\n marker-end=\"url(#arrow-blue)\"\n [attr.marker-start]=\"e.allowBack ? 'url(#arrow-blue-start)' : ''\"\n ></path>\n\n <!-- LABEL -->\n <!-- text b\u00E1m theo line -->\n <!-- \n <text *ngIf=\"e.ActionText\" class=\"edge-label\" dy=\"-6\">\n <textPath [attr.href]=\"'#edge-path-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.ActionText }}\n </textPath>\n </text> \n \n <text *ngIf=\"e.allowBack && e.labelBack\" class=\"edge-label\" dy=\"14\">\n <textPath [attr.href]=\"'#edge-path-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.labelBack }}\n </textPath>\n </text>\n -->\n\n <!-- text th\u1EB3ng n\u1EB1m ngang -->\n <text\n *ngIf=\"e.ActionText\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y - 8\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.ActionText }}\n </text>\n\n <text\n *ngIf=\"e.allowBack && e.labelBack\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y + 14\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.labelBack }}\n </text>\n </ng-container>\n\n <!-- EDGE \u0110ANG HOVER (LU\u00D4N TR\u00CAN C\u00D9NG) -->\n <ng-container *ngFor=\"let e of hoverEdges; let i = index\">\n <path\n class=\"edge-hit\"\n [attr.d]=\"buildPath(e)\"\n (mouseleave)=\"onMouseLeaveEdge(e)\"\n (click)=\"onClickEdge(e, $event)\"\n ></path>\n\n <path\n class=\"edge red\"\n [attr.id]=\"'edge-path-hover-' + i\"\n [attr.d]=\"buildPath(e)\"\n marker-end=\"url(#arrow-red)\"\n [attr.marker-start]=\"e.allowBack ? 'url(#arrow-red-start)' : ''\"\n ></path>\n\n <!-- LABEL -->\n <!-- <text *ngIf=\"e.ActionText\" class=\"edge-label\" dy=\"-6\">\n <textPath [attr.href]=\"'#edge-path-hover-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.ActionText }}\n </textPath>\n </text>\n\n <text *ngIf=\"e.allowBack && e.labelBack\" class=\"edge-label\" dy=\"14\">\n <textPath [attr.href]=\"'#edge-path-hover-' + i\" startOffset=\"50%\" text-anchor=\"middle\">\n {{ e.labelBack }}\n </textPath>\n </text> -->\n\n <text\n *ngIf=\"e.ActionText\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y - 8\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.ActionText }}\n </text>\n\n <text\n *ngIf=\"e.allowBack && e.labelBack\"\n class=\"edge-label\"\n [attr.x]=\"getEdgeLabelPosition(e).x\"\n [attr.y]=\"getEdgeLabelPosition(e).y + 14\"\n [attr.text-anchor]=\"'middle'\"\n >\n {{ e.labelBack }}\n </text>\n </ng-container>\n\n <!-- preview -->\n <path\n *ngIf=\"connectingFrom\"\n class=\"edge\"\n [attr.d]=\"buildPreviewPath()\"\n stroke-dasharray=\"5,5\"\n marker-end=\"url(#arrow-blue)\"\n ></path>\n </g>\n </svg>\n\n <div\n *ngIf=\"isSelecting && selectStart && selectEnd\"\n class=\"selection-box\"\n [style.left.px]=\"Math.min(selectStart.x, selectEnd.x)\"\n [style.top.px]=\"Math.min(selectStart.y, selectEnd.y)\"\n [style.width.px]=\"Math.abs(selectEnd.x - selectStart.x)\"\n [style.height.px]=\"Math.abs(selectEnd.y - selectStart.y)\"\n ></div>\n\n <!-- lstStage -->\n @for (n of lstStage; track $index) {\n @if (n.StageType == \"NODE\") {\n <nz-card\n #nodeEl\n class=\"workflow-node\"\n [attr.data-id]=\"n.Code\"\n [class.selected]=\"n == selectedStage || n == hoverStage || lstSelectedStageCode.has(n.Code)\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n [class.connecting-source]=\"isConnectingFrom(n) || isDraggingFrom(n) || isSelectedNode(n)\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">{{ n.Code }}</div>\n\n <div>{{ n.Name }}</div>\n\n @if (!n.isReverse) {\n <!-- connector RIGHT: output -->\n <div class=\"connector connector-right\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n\n <!-- connector LEFT: input -->\n <div class=\"connector connector-left\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n } @else {\n <div class=\"connector connector-right reverse\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n <div class=\"connector connector-left reverse\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n }\n </nz-card>\n }\n <!-- START NODE -->\n @else if (n.StageType === \"START\") {\n <nz-card\n class=\"workflow-node workflow-node-start\"\n style=\"border: unset\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">&nbsp;</div>\n\n <nz-tag\n [class.selected]=\"n == selectedStage || lstSelectedStageCode.has(n.Code)\"\n style=\"position: relative; top: -13px\"\n [style.right.px]=\"n.isReverseLabel ? -83 : 1\"\n [nzColor]=\"'green'\"\n >\n {{ n.StageType }}\n </nz-tag>\n\n <!-- connector RIGHT: output -->\n <div class=\"connector connector-right connector-start\" (click)=\"onClickConnector($event, n, 'right')\"></div>\n </nz-card>\n }\n\n <!-- END NODE -->\n @else if (n.StageType === \"END\") {\n <nz-card\n class=\"workflow-node workflow-node-end\"\n style=\"border: unset\"\n [style.left.px]=\"n.x\"\n [style.top.px]=\"n.y\"\n [class.connecting-source]=\"isConnectingFrom(n)\"\n nzSize=\"small\"\n (mousedown)=\"onMouseDownNode($event, n)\"\n (click)=\"$event.stopPropagation(); onClickStage(n)\"\n >\n <div class=\"title\">&nbsp;</div>\n\n <nz-tag\n [class.selected]=\"n == selectedStage || lstSelectedStageCode.has(n.Code)\"\n style=\"position: relative; top: -13px\"\n [style.right.px]=\"n.isReverseLabel ? 65 : -5\"\n [nzColor]=\"'red'\"\n >\n {{ n.StageType }}\n </nz-tag>\n\n <!-- connector LEFT: input -->\n <div class=\"connector connector-end\" (click)=\"onClickConnector($event, n, 'left')\"></div>\n </nz-card>\n }\n }\n\n <!-- POINTS -->\n @for (p of connectingPoints; track $index) {\n @if (p != draggingPoint) {\n <div\n class=\"waypoint\"\n [style.left.px]=\"p.x\"\n [style.top.px]=\"p.y\"\n (mousedown)=\"onmousedownPoint($event, p)\"\n (contextmenu)=\"onRightClickPoint($event, hoverEdge!, $index)\"\n ></div>\n } @else {\n <div\n class=\"waypoint dragging\"\n [style.left.px]=\"p.x\"\n [style.top.px]=\"p.y\"\n (mousedown)=\"onmousedownPoint($event, p)\"\n ></div>\n }\n }\n </div>\n</div>\n\n<!-- TEMPLATE -->\n<nz-drawer\n [nzTitle]=\"drawTemplateTitle\"\n nzPlacement=\"right\"\n [nzWidth]=\"500\"\n [nzClosable]=\"false\"\n [nzVisible]=\"drawTemplateVisibel\"\n [nzFooter]=\"footerTplTemplate\"\n (nzOnClose)=\"drawTemplateVisibel = false\"\n>\n <ng-template #drawTemplateTitle> <nz-icon nzType=\"setting\"></nz-icon> &nbsp; Template </ng-template>\n <ng-container *nzDrawerContent>\n <extend-input [label]=\"'Code'\" [(_ngModel)]=\"template.Code\"></extend-input>\n <extend-input [label]=\"'Name'\" [(_ngModel)]=\"template.Name\"></extend-input>\n <extend-select\n [label]=\"'Type'\"\n [lstItem]=\"lstTemplateType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"template.DocType\"\n ></extend-select>\n <extend-textarea [label]=\"'Desscription'\" [(_ngModel)]=\"template.Description\"></extend-textarea>\n <extend-select\n [label]=\"'Print template'\"\n [lstItem]=\"lstTemplatePrint\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"template.TemplatePrint\"\n ></extend-select>\n <extend-checkbox [label]=\"'Active'\" [(_ngModel)]=\"template.IsActive\"></extend-checkbox>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Stage status</h3>\n\n @for (tag of template.lstStageStatus; track tag) {\n <nz-tag\n [nzMode]=\"checkRemoveableStageStatus(tag) ? 'closeable' : 'default'\"\n (nzOnClose)=\"handleCloseStageStatus(tag)\"\n >\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstStageStatus && template.lstStageStatus.length\" />\n\n @if (!inputStageStatusVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInputStageStatus()\">\n <nz-icon nzType=\"plus\" />\n New Action type\n </nz-tag>\n } @else {\n <input\n #inputStageStatusElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Code\"\n [(ngModel)]=\"inputStageStatusCode\"\n (keydown.enter)=\"inputStageStatusNameElement.select()\"\n />\n <input\n #inputStageStatusNameElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Name\"\n [(ngModel)]=\"inputStageStatusName\"\n (blur)=\"handleInputStageStatusConfirm()\"\n (keydown.enter)=\"handleInputStageStatusConfirm()\"\n />\n }\n\n <box [height]=\"16\"></box>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Action type</h3>\n\n @for (tag of template.lstActionType; track tag) {\n <nz-tag\n [nzMode]=\"checkRemoveableActionType(tag) ? 'closeable' : 'default'\"\n (nzOnClose)=\"handleCloseActionType(tag)\"\n >\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstActionType && template.lstActionType.length\" />\n\n @if (!inputActionTypeVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInputActionType()\">\n <nz-icon nzType=\"plus\" />\n New Action type\n </nz-tag>\n } @else {\n <input\n #inputActionTypeElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Code\"\n [(ngModel)]=\"inputActionTypeCode\"\n (keydown.enter)=\"inputActionTypeNameElement.select()\"\n />\n <input\n #inputActionTypeNameElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"Name\"\n [(ngModel)]=\"inputActionTypeName\"\n (blur)=\"handleInputActionTypeConfirm()\"\n (keydown.enter)=\"handleInputActionTypeConfirm()\"\n />\n }\n\n <box [height]=\"16\"></box>\n\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;Action status</h3>\n\n @for (tag of template.lstActionStatus; track tag) {\n <nz-tag [nzMode]=\"checkRemoveableActionStatus(tag) ? 'closeable' : 'default'\" (nzOnClose)=\"handleClose(tag)\">\n {{ sliceTagName(tag.Code + (tag.Name ? \" - \" + tag.Name : \"\")) }}\n </nz-tag>\n }\n\n <br *ngIf=\"template.lstActionStatus && template.lstActionStatus.length\" />\n\n @if (!inputVisible) {\n <nz-tag class=\"editable-tag\" nzNoAnimation (click)=\"showInput()\">\n <nz-icon nzType=\"plus\" />\n New Action status\n </nz-tag>\n } @else {\n <input\n #inputElement\n nz-input\n nzSize=\"small\"\n type=\"text\"\n style=\"width: 150px\"\n placeholder=\"+ New Action status\"\n [(ngModel)]=\"inputValue\"\n (blur)=\"handleInputConfirm()\"\n (keydown.enter)=\"handleInputConfirm()\"\n />\n }\n </ng-container>\n\n <ng-template #footerTplTemplate>\n <div nz-flex [nzGap]=\"6\">\n <button nz-button (click)=\"drawTemplateVisibel = false\">Close</button>\n <button nz-button nzDanger (click)=\"save()\">Save</button>\n </div>\n </ng-template>\n</nz-drawer>\n\n<!-- STAGE -->\n<nz-drawer\n [nzTitle]=\"drawerTitleStage\"\n [nzPlacement]=\"selectedStage?.drawerPosition ? selectedStage?.drawerPosition : 'right'\"\n [nzVisible]=\"!!selectedStage\"\n [nzWidth]=\"750\"\n [nzClosable]=\"false\"\n [nzFooter]=\"footerTplNode\"\n (nzOnClose)=\"selectedStage = undefined\"\n>\n <ng-template #drawerTitleStage>\n <div nz-row nzJustify=\"space-between\">\n @if (selectedStage) {\n @if (selectedStage.drawerPosition == \"left\") {\n <button nz-button nzSize=\"small\" nz-tooltip=\"To the right\" (click)=\"selectedStage.drawerPosition = 'right'\">\n <nz-icon nzType=\"double-right\"></nz-icon>\n </button>\n STAGE\n } @else {\n STAGE\n <button nz-button nzSize=\"small\" nz-tooltip=\"To the left\" (click)=\"selectedStage.drawerPosition = 'left'\">\n <nz-icon nzType=\"double-left\"></nz-icon>\n </button>\n }\n }\n </div>\n </ng-template>\n <ng-container *nzDrawerContent>\n <ng-container *ngIf=\"selectedStage\">\n <nz-radio-group [(ngModel)]=\"selectedStage.StageType\">\n <label nz-radio nzValue=\"START\" [nzDisabled]=\"true\">START</label>\n <label nz-radio nzValue=\"NODE\" [nzDisabled]=\"true\">NODE</label>\n <label nz-radio nzValue=\"END\" [nzDisabled]=\"true\">END</label>\n </nz-radio-group>\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"32\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Code'\"\n [layOutType]=\"'vertical'\"\n [disabled]=\"['START', 'END'].indexOf(selectedStage!.StageType) >= 0\"\n [required]=\"true\"\n [(_ngModel)]=\"selectedStage.Code\"\n ></extend-input>\n\n <extend-textarea\n nz-col\n [nzSpan]=\"16\"\n [label]=\"'Name'\"\n [layOutType]=\"'vertical'\"\n [autoSize]=\"true\"\n [disabled]=\"['START', 'END'].indexOf(selectedStage!.StageType) >= 0\"\n [required]=\"true\"\n [(_ngModel)]=\"selectedStage.Name\"\n ></extend-textarea>\n </div>\n\n <div nz-row [nzGutter]=\"32\">\n <extend-input-number\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Sequence'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedStage.SeqValue\"\n ></extend-input-number>\n\n <extend-select\n nz-col\n [nzSpan]=\"16\"\n [label]=\"'Org implement'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstOrg\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedStage.App_Org_Id\"\n ></extend-select>\n </div>\n\n @if ([\"START\", \"END\"].indexOf(selectedStage.StageType) < 0) {\n <extend-checkbox [label]=\"'Reverse'\" [labelSpan]=\"0\" [(_ngModel)]=\"selectedStage.isReverse\"></extend-checkbox>\n\n <extend-checkbox\n [label]=\"'Require user action'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedStage.IsRequireUser\"\n ></extend-checkbox>\n } @else {\n <extend-checkbox\n [label]=\"'Reverse'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedStage.isReverseLabel\"\n ></extend-checkbox>\n }\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"16\">\n <h3 nz-col><nz-icon nzType=\"send\" nzTheme=\"outline\" />&nbsp;List Action</h3>\n <div nz-col>\n <button nz-button nzType=\"primary\" nzSize=\"small\" (click)=\"addAction()\">\n <nz-icon nzType=\"plus\"></nz-icon>\n </button>\n </div>\n </div>\n\n <nz-table\n nzSize=\"small\"\n [nzData]=\"selectedStage.lstAction || []\"\n [nzShowPagination]=\"false\"\n [nzNoResult]=\"'&nbsp;'\"\n >\n <thead>\n <tr [hidden]=\"true\">\n <th nzWidth=\"50px\"></th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (action of selectedStage.lstAction; track $index) {\n <tr>\n <td>\n <nz-icon\n nzType=\"delete\"\n nzTheme=\"outline\"\n class=\"color-warn cursor-pointer icon-size-16\"\n (click)=\"$event.stopPropagation(); deleteAction(action)\"\n ></nz-icon>\n </td>\n <td (click)=\"hoverEdge = action\">\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"action.ActionText\"\n (_ngModelChange)=\"onchangeActionText(action)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(action)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"action.NextStageStatus\"\n ></extend-select>\n\n <nz-divider\n *ngIf=\"selectedStage.lstAction && $index + 1 < selectedStage.lstAction.length\"\n style=\"margin: 16px 0px 8px 0px; background-color: orange\"\n ></nz-divider>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </nz-table>\n\n <box [height]=\"16\"></box>\n </ng-container>\n </ng-container>\n\n <ng-template #footerTplNode>\n <div nz-flex [nzGap]=\"6\" [nzJustify]=\"selectedStage && selectedStage.drawerPosition == 'left' ? 'end' : 'start'\">\n <button nz-button (click)=\"selectedStage = undefined\">Close</button>\n <button\n *ngIf=\"selectedStage && ['START', 'END'].indexOf(selectedStage.StageType) < 0\"\n nz-button\n nzDanger\n (click)=\"deleteNode()\"\n >\n Delete\n </button>\n </div>\n </ng-template>\n</nz-drawer>\n\n<!-- EDGE -->\n<nz-drawer\n nzTitle=\"ACTION\"\n nzPlacement=\"right\"\n [nzClosable]=\"false\"\n [nzWidth]=\"750\"\n [nzVisible]=\"!!selectedAction\"\n (nzOnClose)=\"selectedAction = undefined\"\n>\n <ng-container *nzDrawerContent>\n <ng-container *ngIf=\"selectedAction\">\n <strong>{{ selectedAction.FromStage }} -> {{ selectedAction.ToStage }}</strong>\n\n <box [height]=\"16\"></box>\n\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedAction.ActionText\"\n (_ngModelChange)=\"onchangeActionText(selectedAction)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(selectedAction)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedAction.NextStageStatus\"\n ></extend-select>\n </div>\n\n <!-- <box [height]=\"16\"></box> -->\n <nz-divider></nz-divider>\n\n <extend-checkbox\n [label]=\"'Allow back'\"\n [labelSpan]=\"0\"\n [(_ngModel)]=\"selectedAction.allowBack\"\n (_ngModelChange)=\"onchangeAllowBack(selectedAction)\"\n ></extend-checkbox>\n\n <box [height]=\"16\"></box>\n\n @if (selectedAction.allowBack) {\n <strong>{{ selectedAction.ToStage }} -> {{ selectedAction.FromStage }}</strong>\n\n @if (selectedBackAction) {\n <div nz-row [nzGutter]=\"16\" class=\"form-item-no-bottom\">\n <extend-input\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action text'\"\n [layOutType]=\"'vertical'\"\n [(_ngModel)]=\"selectedBackAction.ActionText\"\n (_ngModelChange)=\"onchangeActionText(selectedBackAction)\"\n ></extend-input>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action type'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionType\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ActionType\"\n ></extend-select>\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Action status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstActionStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ActionStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.StageStatus\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"lstStage\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.ToStage\"\n (_ngModelChange)=\"onchangeNextStage(selectedBackAction)\"\n ></extend-select>\n\n <extend-select\n nz-col\n [nzSpan]=\"8\"\n [label]=\"'Next stage status'\"\n [layOutType]=\"'vertical'\"\n [lstItem]=\"template.lstStageStatus\"\n [valueField]=\"'Code'\"\n [displayFields]=\"['Code', 'Name']\"\n [(_ngModel)]=\"selectedBackAction.NextStageStatus\"\n ></extend-select>\n </div>\n }\n }\n </ng-container>\n </ng-container>\n</nz-drawer>\n\n<!-- setting editor -->\n<nz-drawer\n [nzTitle]=\"drawSettingTitle\"\n nzPlacement=\"right\"\n [nzClosable]=\"false\"\n [nzVisible]=\"settingVisible\"\n (nzOnClose)=\"settingVisible = false\"\n>\n <ng-template #drawSettingTitle> <nz-icon nzType=\"setting\"></nz-icon> &nbsp; Editor </ng-template>\n <ng-container *nzDrawerContent>\n <div class=\"form-item-no-bottom\">\n <extend-checkbox [label]=\"'Show grid'\" [labelSpan]=\"0\" [(_ngModel)]=\"wfcSetting.ShowGrid\"></extend-checkbox>\n\n <box [height]=\"16\"></box>\n\n <extend-input-number [label]=\"'Canvas width'\" [(_ngModel)]=\"CANVAS_WIDTH\"></extend-input-number>\n <box [height]=\"16\"></box>\n <extend-input-number [label]=\"'Canvas height'\" [(_ngModel)]=\"CANVAS_HEIGHT\"></extend-input-number>\n </div>\n </ng-container>\n</nz-drawer>\n", styles: ["@charset \"UTF-8\";.workflow-canvas{position:relative;width:100%;height:600px;background:#f0f2f5;cursor:default;overflow:hidden;outline:1px dashed rgba(0,0,0,.1)}.workflow-canvas .edges-layer{position:absolute;inset:0;width:100%;height:100%;z-index:1}.workflow-canvas .edges-layer path{stroke-width:2;fill:none}.canvas-content{transform-origin:0 0;width:10000px;height:10000px}.canvas-content{outline:1px dashed red}.canvas-content.panning{cursor:grab}.canvas-content.panning:active{cursor:grabbing}.workflow-canvas.panning{cursor:grab}.workflow-canvas.panning:active{cursor:grabbing}.workflow-canvas.connecting,.workflow-canvas.connecting .workflow-node{cursor:crosshair}.workflow-node{position:absolute;width:160px;cursor:grab;-webkit-user-select:none;user-select:none;z-index:2}.workflow-node:active{cursor:grabbing}.workflow-node .title{font-weight:600;margin-bottom:6px}.workflow-node.connecting-source,.workflow-node.selected,.workflow-node:hover:not(.workflow-node-start,.workflow-node-end){outline:2px dashed #1890ff}.workflow-node-start{background-color:unset;display:flex;justify-content:right}.workflow-node-end{background-color:unset;display:flex}.connector{position:absolute;top:39px;width:12px;height:12px;background:#1890ff;border-radius:50%;transform:translateY(-50%);cursor:crosshair;transition:box-shadow .15s ease-out,transform .15s ease-out}.connector:hover{box-shadow:0 0 0 3px #1890ff4d;transform:translateY(-50%) scale(1.15)}.connector-right{right:-8px;background:orange}.connector-right.reverse,.connector-left{left:-8px}.connector-left.reverse{left:unset;right:-8px}.connector-start{width:16px;height:16px;right:-3px;background:green;box-shadow:0 0 0 3px #1890ff4d}.connector-end{width:16px;height:16px;left:-6px;background:#000;box-shadow:0 0 0 3px #1890ff4d}.edge{cursor:pointer;stroke:#1890ff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2;fill:none;transition:stroke .15s ease}.edge-hit{stroke:transparent;stroke-width:18px!important;fill:none;cursor:pointer}.edge-hit:hover+.edge{stroke:#ff4d4f;stroke-width:2}.workflow-canvas.dragging-point .edge-hit{pointer-events:none}.edge:hover+.edge-label{fill:#1890ff;font-weight:500}.edge-label{font-size:12px;fill:#555;pointer-events:none;-webkit-user-select:none;user-select:none}.blue{stroke:#1890ff!important}.red{stroke:#ff4d4f!important;box-shadow:0 0 0 3px #1890ff4d!important}.workflow-svg{background:transparent;overflow:visible}.waypoint{position:absolute;width:10px;height:10px;background:#ff4d4f;border-radius:50%;transform:translate(-50%,-50%) scale(1);cursor:move;z-index:1;transition:transform .12s ease-out,background-color .12s ease-out,box-shadow .12s ease-out}.waypoint:hover{background:#ff7875;transform:translate(-50%,-50%) scale(1.25);box-shadow:0 0 0 4px #ff787559}.waypoint.dragging{transition:none;transform:translate(-50%,-50%) scale(1.15);pointer-events:none}.workflow-canvas.dragging-point svg{cursor:grabbing}.selection-box{position:absolute;border:1px dashed #1890ff;background:#1890ff1a;pointer-events:none}.toolbar{position:absolute;top:8px;left:8px;display:flex;gap:4px;padding:6px;background:#fff;border-radius:6px;box-shadow:0 2px 8px #00000026;z-index:100}.toolbar .divider{width:1px;background:#e5e5e5;margin:0 2px}.zoom-toolbar{position:absolute;top:8px;left:8px;z-index:10;display:flex;align-items:center;gap:6px;background:#fff;padding:4px 6px;border-radius:6px;box-shadow:0 2px 6px #00000026}.zoom-toolbar span{min-width:40px;text-align:center;font-size:12px}nz-tag.selected{outline:2px dashed #1890ff}.grid-layer{position:absolute;inset:0;pointer-events:none;z-index:0;background-image:radial-gradient(rgba(0,0,0,.08) 1px,transparent 1px);background-size:20px 20px}.grid-layer{background-image:linear-gradient(to right,rgba(0,0,0,.06) 1px,transparent 1px),linear-gradient(to bottom,rgba(0,0,0,.06) 1px,transparent 1px);background-size:20px 20px}.editable-tag{background:#fff;border-style:dashed}\n"] }]
4269
+ }], propDecorators: { lstOrg: [{
4270
+ type: Input
4271
+ }], lstTemplateType: [{
4272
+ type: Input
4273
+ }], lstTemplatePrint: [{
4274
+ type: Input
4275
+ }], template: [{
4276
+ type: Input
4277
+ }], lstStage: [{
4278
+ type: Input
4279
+ }], lstAction: [{
4280
+ type: Input
4281
+ }], onSave: [{
4282
+ type: Output,
4283
+ args: ['onSave']
4284
+ }], canvasRef: [{
4285
+ type: ViewChild,
4286
+ args: ['canvas', { static: true }]
4287
+ }], svgRef: [{
4288
+ type: ViewChild,
4289
+ args: ['svg', { static: true }]
4290
+ }], onKeyDown: [{
4291
+ type: HostListener,
4292
+ args: ['window:keydown', ['$event']]
4293
+ }], inputStageStatusElement: [{
4294
+ type: ViewChild,
4295
+ args: ['inputStageStatusElement', { static: false }]
4296
+ }], inputActionTypeElement: [{
4297
+ type: ViewChild,
4298
+ args: ['inputActionTypeElement', { static: false }]
4299
+ }], inputElement: [{
4300
+ type: ViewChild,
4301
+ args: ['inputElement', { static: false }]
4302
+ }] } });
4303
+
2683
4304
  class AutoFocusDirective {
2684
4305
  el;
2685
4306
  renderer;
@@ -3200,7 +4821,7 @@ class RouteMonitorService {
3200
4821
  this.router.events.subscribe(async (event) => {
3201
4822
  if (event instanceof NavigationEnd) {
3202
4823
  const url = event.urlAfterRedirects;
3203
- if (url.startsWith('/') || url.startsWith('/login') || url == '/no-permission') {
4824
+ if (url == '/' || url.startsWith('/login') || url == '/no-permission') {
3204
4825
  return;
3205
4826
  }
3206
4827
  const hasMenu = await this.authService.checkUserMenu(url);
@@ -3327,5 +4948,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.7", ngImpor
3327
4948
  * Generated bundle index. Do not edit.
3328
4949
  */
3329
4950
 
3330
- export { AnimatedDigitComponent, AppGlobals, AppStorage, AuthGuard, AutoFocusDirective, BarGraphComponent, BaseComponent, BaseGuardChangeComponent, BaseOverlayComponent, Box, Breadcrumb, CheckModel, CommonService, DashcardComponent, DateInputParserDirective, DateTimeHelper, DoughnutGraphComponent, DownloadHelper, ENUM_ResponseType, ExtendCheckbox, ExtendDatePicker, ExtendDateRangePicker, ExtendInput, ExtendInputNumber, ExtendSelectComponent, ExtendTextArea, GlobalSpinnerComponent, GridFilter, HTTPService, Height, LimitWordsPipe, LineGraphComponent, LoadingService, NoPermission, NotiService, NullIfEmptyDirective, NumberOnlyDirective, OrderOption, PagingData, PagingModel, PdfViewerComponent, RouteMonitorService, TableHeader, ThemeService, TokenStorage, TranslateKey, UnsavedChangesGuard, UpperCaseFirsLetterEachWordDirective, UppercaseDirective, UppercaseFirstLetterDirective, VscService, Width, XLSXHelper, authInterceptor };
4951
+ export { AnimatedDigitComponent, AppGlobals, AppStorage, AuthGuard, AutoFocusDirective, BarGraphComponent, BaseComponent, BaseGuardChangeComponent, BaseOverlayComponent, Box, Breadcrumb, CheckModel, CommonService, DashcardComponent, DateInputParserDirective, DateTimeHelper, DoughnutGraphComponent, DownloadHelper, ENUM_ResponseType, ExtendCheckbox, ExtendDatePicker, ExtendDateRangePicker, ExtendInput, ExtendInputNumber, ExtendSelectComponent, ExtendTextArea, GlobalSpinnerComponent, GridFilter, HTTPService, Height, LimitWordsPipe, LineGraphComponent, LoadingService, NoPermission, NotiService, NullIfEmptyDirective, NumberOnlyDirective, OrderOption, PagingData, PagingModel, PdfViewerComponent, RouteMonitorService, TableHeader, ThemeService, TokenStorage, TranslateKey, UnsavedChangesGuard, UpperCaseFirsLetterEachWordDirective, UppercaseDirective, UppercaseFirstLetterDirective, VscService, Width, WorkflowEditorComponent, XLSXHelper, authInterceptor };
3331
4952
  //# sourceMappingURL=brggroup-share-lib.mjs.map