@memberjunction/ng-file-storage 5.22.0 → 5.23.0

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.
@@ -1,8 +1,6 @@
1
1
  import { EventEmitter, OnInit } from '@angular/core';
2
2
  import { MJFileCategoryEntity } from '@memberjunction/core-entities';
3
3
  import { SharedService } from '@memberjunction/ng-shared';
4
- import { ContextMenuSelectEvent } from '@progress/kendo-angular-menu';
5
- import { TreeItemAddRemoveArgs } from '@progress/kendo-angular-treeview';
6
4
  import * as i0 from "@angular/core";
7
5
  export declare class CategoryTreeComponent implements OnInit {
8
6
  private sharedService;
@@ -10,19 +8,45 @@ export declare class CategoryTreeComponent implements OnInit {
10
8
  isLoading: boolean;
11
9
  showNew: boolean;
12
10
  newCategoryName: string;
13
- selectedKeys: never[];
14
11
  renameFileCategory: MJFileCategoryEntity | undefined;
15
12
  categoriesData: MJFileCategoryEntity[];
13
+ /** Expanded node IDs for the tree */
14
+ expandedIds: Set<string>;
15
+ /** Currently selected node ID */
16
+ private selectedId;
17
+ /** Context menu state */
18
+ contextMenuVisible: boolean;
19
+ contextMenuX: number;
20
+ contextMenuY: number;
21
+ private contextMenuNode;
16
22
  private md;
17
23
  constructor(sharedService: SharedService);
18
24
  ngOnInit(): void;
25
+ /** Returns root-level nodes (no parent). */
26
+ get rootNodes(): MJFileCategoryEntity[];
27
+ /** Checks if a node has children. */
28
+ hasChildren(node: MJFileCategoryEntity): boolean;
29
+ /** Returns children of a node. */
30
+ getChildren(node: MJFileCategoryEntity): MJFileCategoryEntity[];
31
+ /** Checks if a node is expanded. */
32
+ isExpanded(node: MJFileCategoryEntity): boolean;
33
+ /** Toggles expand/collapse on a node. */
34
+ toggleExpand(node: MJFileCategoryEntity, event: Event): void;
35
+ /** Checks if a node is the currently selected node. */
36
+ isSelected(node: MJFileCategoryEntity): boolean;
37
+ /** Selects a node and emits event. */
38
+ selectNode(node: MJFileCategoryEntity): void;
39
+ /** Opens context menu on right-click. */
40
+ onContextMenu(event: MouseEvent, node: MJFileCategoryEntity): void;
41
+ /** Closes the context menu. */
42
+ closeContextMenu(): void;
43
+ /** Handles context menu action selection. */
44
+ onContextMenuAction(action: string): void;
19
45
  createNewCategory(): Promise<void>;
20
46
  cancelNewCategory(): void;
21
- handleDrop(e: TreeItemAddRemoveArgs): Promise<void>;
22
47
  saveNewCategory(): Promise<void>;
23
48
  deleteCategory(fileCategory: MJFileCategoryEntity): Promise<void>;
24
49
  clearSelection(): void;
25
- handleMenuSelect(e: ContextMenuSelectEvent): void;
26
50
  cancelRename(): void;
27
51
  saveRename(): Promise<void>;
28
52
  Refresh(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"category-tree.d.ts","sourceRoot":"","sources":["../../../src/lib/category-tree/category-tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAExE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;;AAGzE,qBAMa,qBAAsB,YAAW,MAAM;IAatC,OAAO,CAAC,aAAa;IAZvB,gBAAgB,mCAA0C;IAE7D,SAAS,EAAE,OAAO,CAAS;IAC3B,OAAO,EAAE,OAAO,CAAS;IACzB,eAAe,SAAM;IACrB,YAAY,UAAM;IAClB,kBAAkB,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAErD,cAAc,EAAE,oBAAoB,EAAE,CAAM;IAEnD,OAAO,CAAC,EAAE,CAAkB;gBAER,aAAa,EAAE,aAAa;IAEhD,QAAQ,IAAI,IAAI;IAIV,iBAAiB;IAIvB,iBAAiB;IAIX,UAAU,CAAC,CAAC,EAAE,qBAAqB;IAWnC,eAAe;IAWf,cAAc,CAAC,YAAY,EAAE,oBAAoB;IAevD,cAAc;IAKd,gBAAgB,CAAC,CAAC,EAAE,sBAAsB;IAgB1C,YAAY;IAKN,UAAU;IAOV,OAAO;yCAjGF,qBAAqB;2CAArB,qBAAqB;CAiHjC"}
1
+ {"version":3,"file":"category-tree.d.ts","sourceRoot":"","sources":["../../../src/lib/category-tree/category-tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,MAAM,EAAU,MAAM,eAAe,CAAC;AAExE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;;AAG1D,qBAMa,qBAAsB,YAAW,MAAM;IAwBtC,OAAO,CAAC,aAAa;IAvBvB,gBAAgB,mCAA0C;IAE7D,SAAS,EAAE,OAAO,CAAS;IAC3B,OAAO,EAAE,OAAO,CAAS;IACzB,eAAe,SAAM;IACrB,kBAAkB,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAErD,cAAc,EAAE,oBAAoB,EAAE,CAAM;IAEnD,qCAAqC;IAC9B,WAAW,cAAqB;IAEvC,iCAAiC;IACjC,OAAO,CAAC,UAAU,CAAqB;IAEvC,yBAAyB;IAClB,kBAAkB,UAAS;IAC3B,YAAY,SAAK;IACjB,YAAY,SAAK;IACxB,OAAO,CAAC,eAAe,CAAmC;IAE1D,OAAO,CAAC,EAAE,CAAkB;gBAER,aAAa,EAAE,aAAa;IAEhD,QAAQ,IAAI,IAAI;IAIhB,4CAA4C;IAC5C,IAAI,SAAS,IAAI,oBAAoB,EAAE,CAEtC;IAED,qCAAqC;IACrC,WAAW,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO;IAIhD,kCAAkC;IAClC,WAAW,CAAC,IAAI,EAAE,oBAAoB,GAAG,oBAAoB,EAAE;IAI/D,oCAAoC;IACpC,UAAU,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO;IAI/C,yCAAyC;IACzC,YAAY,CAAC,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAS5D,uDAAuD;IACvD,UAAU,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO;IAI/C,sCAAsC;IACtC,UAAU,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI;IAK5C,yCAAyC;IACzC,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,oBAAoB,GAAG,IAAI;IASlE,+BAA+B;IAC/B,gBAAgB,IAAI,IAAI;IAKxB,6CAA6C;IAC7C,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAiBnC,iBAAiB;IAIvB,iBAAiB;IAIX,eAAe;IAYf,cAAc,CAAC,YAAY,EAAE,oBAAoB;IAgBvD,cAAc;IAKd,YAAY;IAKN,UAAU;IAOV,OAAO;yCA9JF,qBAAqB;2CAArB,qBAAqB;CA8KjC"}
@@ -3,85 +3,128 @@ import { Metadata, RunView } from '@memberjunction/core';
3
3
  import { UUIDsEqual } from '@memberjunction/global';
4
4
  import * as i0 from "@angular/core";
5
5
  import * as i1 from "@memberjunction/ng-shared";
6
- import * as i2 from "@progress/kendo-angular-treeview";
6
+ import * as i2 from "@angular/common";
7
7
  import * as i3 from "@angular/forms";
8
- import * as i4 from "@progress/kendo-angular-dialog";
9
- import * as i5 from "@progress/kendo-angular-buttons";
10
- import * as i6 from "@progress/kendo-angular-label";
11
- import * as i7 from "@progress/kendo-angular-inputs";
12
- import * as i8 from "@progress/kendo-angular-menu";
13
- function CategoryTreeComponent_ng_template_7_Template(rf, ctx) { if (rf & 1) {
14
- const _r1 = i0.ɵɵgetCurrentView();
15
- i0.ɵɵelementStart(0, "span", null, 0);
16
- i0.ɵɵtext(2);
8
+ import * as i4 from "@memberjunction/ng-ui-components";
9
+ const _c0 = a0 => ({ $implicit: a0, level: 0 });
10
+ const _c1 = (a0, a1) => ({ $implicit: a0, level: a1 });
11
+ const _forTrack0 = ($index, $item) => $item.ID;
12
+ function CategoryTreeComponent_For_8_ng_container_0_Template(rf, ctx) { if (rf & 1) {
13
+ i0.ɵɵelementContainer(0);
14
+ } }
15
+ function CategoryTreeComponent_For_8_Template(rf, ctx) { if (rf & 1) {
16
+ i0.ɵɵtemplate(0, CategoryTreeComponent_For_8_ng_container_0_Template, 1, 0, "ng-container", 17);
17
+ } if (rf & 2) {
18
+ const node_r2 = ctx.$implicit;
19
+ i0.ɵɵnextContext();
20
+ const treeNode_r3 = i0.ɵɵreference(10);
21
+ i0.ɵɵproperty("ngTemplateOutlet", treeNode_r3)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c0, node_r2));
22
+ } }
23
+ function CategoryTreeComponent_ng_template_9_Conditional_2_Template(rf, ctx) { if (rf & 1) {
24
+ const _r7 = i0.ɵɵgetCurrentView();
25
+ i0.ɵɵelementStart(0, "button", 24);
26
+ i0.ɵɵlistener("click", function CategoryTreeComponent_ng_template_9_Conditional_2_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r7); const node_r5 = i0.ɵɵnextContext().$implicit; const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.toggleExpand(node_r5, $event)); });
27
+ i0.ɵɵelement(1, "i", 25);
17
28
  i0.ɵɵelementEnd();
18
- i0.ɵɵelementStart(3, "kendo-contextmenu", 10);
19
- i0.ɵɵlistener("select", function CategoryTreeComponent_ng_template_7_Template_kendo_contextmenu_select_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.handleMenuSelect($event)); });
20
- i0.ɵɵelement(4, "kendo-menu-item", 11)(5, "kendo-menu-item", 12);
29
+ } if (rf & 2) {
30
+ const node_r5 = i0.ɵɵnextContext().$implicit;
31
+ const ctx_r5 = i0.ɵɵnextContext();
32
+ i0.ɵɵadvance();
33
+ i0.ɵɵclassProp("fa-chevron-right", !ctx_r5.isExpanded(node_r5))("fa-chevron-down", ctx_r5.isExpanded(node_r5));
34
+ } }
35
+ function CategoryTreeComponent_ng_template_9_Conditional_3_Template(rf, ctx) { if (rf & 1) {
36
+ i0.ɵɵelement(0, "span", 21);
37
+ } }
38
+ function CategoryTreeComponent_ng_template_9_Conditional_7_For_1_ng_container_0_Template(rf, ctx) { if (rf & 1) {
39
+ i0.ɵɵelementContainer(0);
40
+ } }
41
+ function CategoryTreeComponent_ng_template_9_Conditional_7_For_1_Template(rf, ctx) { if (rf & 1) {
42
+ i0.ɵɵtemplate(0, CategoryTreeComponent_ng_template_9_Conditional_7_For_1_ng_container_0_Template, 1, 0, "ng-container", 17);
43
+ } if (rf & 2) {
44
+ const child_r8 = ctx.$implicit;
45
+ const level_r9 = i0.ɵɵnextContext(2).level;
46
+ i0.ɵɵnextContext();
47
+ const treeNode_r3 = i0.ɵɵreference(10);
48
+ i0.ɵɵproperty("ngTemplateOutlet", treeNode_r3)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c1, child_r8, level_r9 + 1));
49
+ } }
50
+ function CategoryTreeComponent_ng_template_9_Conditional_7_Template(rf, ctx) { if (rf & 1) {
51
+ i0.ɵɵrepeaterCreate(0, CategoryTreeComponent_ng_template_9_Conditional_7_For_1_Template, 1, 5, "ng-container", null, _forTrack0);
52
+ } if (rf & 2) {
53
+ const node_r5 = i0.ɵɵnextContext().$implicit;
54
+ const ctx_r5 = i0.ɵɵnextContext();
55
+ i0.ɵɵrepeater(ctx_r5.getChildren(node_r5));
56
+ } }
57
+ function CategoryTreeComponent_ng_template_9_Template(rf, ctx) { if (rf & 1) {
58
+ const _r4 = i0.ɵɵgetCurrentView();
59
+ i0.ɵɵelementStart(0, "div", 18)(1, "div", 19);
60
+ i0.ɵɵlistener("click", function CategoryTreeComponent_ng_template_9_Template_div_click_1_listener() { const node_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.selectNode(node_r5)); })("contextmenu", function CategoryTreeComponent_ng_template_9_Template_div_contextmenu_1_listener($event) { const node_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.onContextMenu($event, node_r5)); });
61
+ i0.ɵɵconditionalCreate(2, CategoryTreeComponent_ng_template_9_Conditional_2_Template, 2, 4, "button", 20)(3, CategoryTreeComponent_ng_template_9_Conditional_3_Template, 1, 0, "span", 21);
62
+ i0.ɵɵelement(4, "i", 22);
63
+ i0.ɵɵelementStart(5, "span", 23);
64
+ i0.ɵɵtext(6);
65
+ i0.ɵɵelementEnd()();
66
+ i0.ɵɵconditionalCreate(7, CategoryTreeComponent_ng_template_9_Conditional_7_Template, 2, 0);
21
67
  i0.ɵɵelementEnd();
22
68
  } if (rf & 2) {
23
- const dataItem_r3 = ctx.$implicit;
24
- const target_r4 = i0.ɵɵreference(1);
25
- i0.ɵɵadvance(2);
26
- i0.ɵɵtextInterpolate1("", dataItem_r3.Name, " ");
69
+ const node_r5 = ctx.$implicit;
70
+ const level_r9 = ctx.level;
71
+ const ctx_r5 = i0.ɵɵnextContext();
72
+ i0.ɵɵstyleProp("padding-left", level_r9 * 20, "px");
27
73
  i0.ɵɵadvance();
28
- i0.ɵɵproperty("target", target_r4);
74
+ i0.ɵɵclassProp("selected", ctx_r5.isSelected(node_r5));
29
75
  i0.ɵɵadvance();
30
- i0.ɵɵproperty("data", dataItem_r3);
76
+ i0.ɵɵconditional(ctx_r5.hasChildren(node_r5) ? 2 : 3);
77
+ i0.ɵɵadvance(4);
78
+ i0.ɵɵtextInterpolate(node_r5.Name);
31
79
  i0.ɵɵadvance();
32
- i0.ɵɵproperty("data", dataItem_r3)("disabled", true);
80
+ i0.ɵɵconditional(ctx_r5.isExpanded(node_r5) ? 7 : -1);
33
81
  } }
34
- function CategoryTreeComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
35
- const _r5 = i0.ɵɵgetCurrentView();
36
- i0.ɵɵelementStart(0, "kendo-dialog", 13);
37
- i0.ɵɵlistener("close", function CategoryTreeComponent_Conditional_8_Template_kendo_dialog_close_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelNewCategory()); });
38
- i0.ɵɵelementStart(1, "div", 2)(2, "form", null, 1)(4, "kendo-label", 14)(5, "kendo-textbox", 15);
39
- i0.ɵɵtwoWayListener("ngModelChange", function CategoryTreeComponent_Conditional_8_Template_kendo_textbox_ngModelChange_5_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r1.newCategoryName, $event) || (ctx_r1.newCategoryName = $event); return i0.ɵɵresetView($event); });
40
- i0.ɵɵelementEnd()()()();
41
- i0.ɵɵelementStart(6, "kendo-dialog-actions")(7, "button", 16);
42
- i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_8_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelNewCategory()); });
43
- i0.ɵɵtext(8, "Cancel");
82
+ function CategoryTreeComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
83
+ const _r10 = i0.ɵɵgetCurrentView();
84
+ i0.ɵɵelementStart(0, "div", 26);
85
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_11_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.closeContextMenu()); }, i0.ɵɵresolveDocument);
86
+ i0.ɵɵelementStart(1, "button", 27);
87
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_11_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r10); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.onContextMenuAction("rename")); });
88
+ i0.ɵɵelement(2, "i", 28);
89
+ i0.ɵɵtext(3, " Rename ");
44
90
  i0.ɵɵelementEnd();
45
- i0.ɵɵelementStart(9, "button", 17);
46
- i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_8_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.saveNewCategory()); });
47
- i0.ɵɵtext(10, "Save");
48
- i0.ɵɵelementEnd()()();
91
+ i0.ɵɵelementStart(4, "button", 29);
92
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_11_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r10); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.onContextMenuAction("delete")); });
93
+ i0.ɵɵelement(5, "i", 30);
94
+ i0.ɵɵtext(6, " Delete ");
95
+ i0.ɵɵelementEnd()();
49
96
  } if (rf & 2) {
50
- const ctx_r1 = i0.ɵɵnextContext();
51
- i0.ɵɵproperty("minWidth", 250)("width", 450);
52
- i0.ɵɵadvance(5);
53
- i0.ɵɵtwoWayProperty("ngModel", ctx_r1.newCategoryName);
54
- i0.ɵɵproperty("showSuccessIcon", ctx_r1.newCategoryName.length > 0)("disabled", ctx_r1.isLoading);
55
- i0.ɵɵadvance(2);
56
- i0.ɵɵproperty("disabled", ctx_r1.isLoading);
57
- i0.ɵɵadvance(2);
58
- i0.ɵɵproperty("disabled", ctx_r1.isLoading);
97
+ const ctx_r5 = i0.ɵɵnextContext();
98
+ i0.ɵɵstyleProp("top", ctx_r5.contextMenuY, "px")("left", ctx_r5.contextMenuX, "px");
59
99
  } }
60
- function CategoryTreeComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
61
- const _r6 = i0.ɵɵgetCurrentView();
62
- i0.ɵɵelementStart(0, "kendo-dialog", 18);
63
- i0.ɵɵlistener("close", function CategoryTreeComponent_Conditional_9_Template_kendo_dialog_close_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelRename()); });
64
- i0.ɵɵelementStart(1, "div", 2)(2, "form", null, 1)(4, "kendo-label", 14)(5, "kendo-textbox", 19);
65
- i0.ɵɵtwoWayListener("ngModelChange", function CategoryTreeComponent_Conditional_9_Template_kendo_textbox_ngModelChange_5_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r1.renameFileCategory.Name, $event) || (ctx_r1.renameFileCategory.Name = $event); return i0.ɵɵresetView($event); });
66
- i0.ɵɵelementEnd()()()();
67
- i0.ɵɵelementStart(6, "kendo-dialog-actions")(7, "button", 16);
68
- i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_9_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelRename()); });
69
- i0.ɵɵtext(8, "Cancel");
100
+ function CategoryTreeComponent_Conditional_25_Template(rf, ctx) { if (rf & 1) {
101
+ const _r11 = i0.ɵɵgetCurrentView();
102
+ i0.ɵɵelementStart(0, "mj-dialog", 31);
103
+ i0.ɵɵlistener("Close", function CategoryTreeComponent_Conditional_25_Template_mj_dialog_Close_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.cancelRename()); });
104
+ i0.ɵɵelementStart(1, "div", 10)(2, "form", null, 2)(4, "label", 11)(5, "span", 12);
105
+ i0.ɵɵtext(6, "Name");
70
106
  i0.ɵɵelementEnd();
71
- i0.ɵɵelementStart(9, "button", 17);
72
- i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_9_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.saveRename()); });
107
+ i0.ɵɵelementStart(7, "input", 32);
108
+ i0.ɵɵtwoWayListener("ngModelChange", function CategoryTreeComponent_Conditional_25_Template_input_ngModelChange_7_listener($event) { i0.ɵɵrestoreView(_r11); const ctx_r5 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r5.renameFileCategory.Name, $event) || (ctx_r5.renameFileCategory.Name = $event); return i0.ɵɵresetView($event); });
109
+ i0.ɵɵelementEnd()()()();
110
+ i0.ɵɵelementStart(8, "mj-dialog-actions")(9, "button", 14);
111
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_25_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r11); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.saveRename()); });
73
112
  i0.ɵɵtext(10, "Save");
113
+ i0.ɵɵelementEnd();
114
+ i0.ɵɵelementStart(11, "button", 15);
115
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Conditional_25_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r11); const ctx_r5 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r5.cancelRename()); });
116
+ i0.ɵɵtext(12, "Cancel");
74
117
  i0.ɵɵelementEnd()()();
75
118
  } if (rf & 2) {
76
- const ctx_r1 = i0.ɵɵnextContext();
77
- i0.ɵɵproperty("minWidth", 250)("width", 450);
78
- i0.ɵɵadvance(5);
79
- i0.ɵɵtwoWayProperty("ngModel", ctx_r1.renameFileCategory.Name);
80
- i0.ɵɵproperty("showSuccessIcon", ctx_r1.renameFileCategory.Name.length > 0)("disabled", ctx_r1.isLoading);
119
+ const ctx_r5 = i0.ɵɵnextContext();
120
+ i0.ɵɵproperty("Visible", true)("Width", 450)("MinWidth", 250);
121
+ i0.ɵɵadvance(7);
122
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r5.renameFileCategory.Name);
123
+ i0.ɵɵproperty("disabled", ctx_r5.isLoading);
81
124
  i0.ɵɵadvance(2);
82
- i0.ɵɵproperty("disabled", ctx_r1.isLoading);
125
+ i0.ɵɵproperty("disabled", ctx_r5.isLoading || !ctx_r5.renameFileCategory.Dirty);
83
126
  i0.ɵɵadvance(2);
84
- i0.ɵɵproperty("disabled", ctx_r1.isLoading || !ctx_r1.renameFileCategory.Dirty);
127
+ i0.ɵɵproperty("disabled", ctx_r5.isLoading);
85
128
  } }
86
129
  export class CategoryTreeComponent {
87
130
  sharedService;
@@ -89,9 +132,17 @@ export class CategoryTreeComponent {
89
132
  isLoading = false;
90
133
  showNew = false;
91
134
  newCategoryName = '';
92
- selectedKeys = [];
93
135
  renameFileCategory;
94
136
  categoriesData = [];
137
+ /** Expanded node IDs for the tree */
138
+ expandedIds = new Set();
139
+ /** Currently selected node ID */
140
+ selectedId;
141
+ /** Context menu state */
142
+ contextMenuVisible = false;
143
+ contextMenuX = 0;
144
+ contextMenuY = 0;
145
+ contextMenuNode;
95
146
  md = new Metadata();
96
147
  constructor(sharedService) {
97
148
  this.sharedService = sharedService;
@@ -99,21 +150,77 @@ export class CategoryTreeComponent {
99
150
  ngOnInit() {
100
151
  this.Refresh();
101
152
  }
153
+ /** Returns root-level nodes (no parent). */
154
+ get rootNodes() {
155
+ return this.categoriesData.filter((c) => !c.ParentID);
156
+ }
157
+ /** Checks if a node has children. */
158
+ hasChildren(node) {
159
+ return this.categoriesData.some((c) => UUIDsEqual(c.ParentID, node.ID));
160
+ }
161
+ /** Returns children of a node. */
162
+ getChildren(node) {
163
+ return this.categoriesData.filter((c) => UUIDsEqual(c.ParentID, node.ID));
164
+ }
165
+ /** Checks if a node is expanded. */
166
+ isExpanded(node) {
167
+ return this.expandedIds.has(node.ID);
168
+ }
169
+ /** Toggles expand/collapse on a node. */
170
+ toggleExpand(node, event) {
171
+ event.stopPropagation();
172
+ if (this.expandedIds.has(node.ID)) {
173
+ this.expandedIds.delete(node.ID);
174
+ }
175
+ else {
176
+ this.expandedIds.add(node.ID);
177
+ }
178
+ }
179
+ /** Checks if a node is the currently selected node. */
180
+ isSelected(node) {
181
+ return this.selectedId != null && UUIDsEqual(this.selectedId, node.ID);
182
+ }
183
+ /** Selects a node and emits event. */
184
+ selectNode(node) {
185
+ this.selectedId = node.ID;
186
+ this.categorySelected.emit(node.ID);
187
+ }
188
+ /** Opens context menu on right-click. */
189
+ onContextMenu(event, node) {
190
+ event.preventDefault();
191
+ event.stopPropagation();
192
+ this.contextMenuNode = node;
193
+ this.contextMenuX = event.clientX;
194
+ this.contextMenuY = event.clientY;
195
+ this.contextMenuVisible = true;
196
+ }
197
+ /** Closes the context menu. */
198
+ closeContextMenu() {
199
+ this.contextMenuVisible = false;
200
+ this.contextMenuNode = undefined;
201
+ }
202
+ /** Handles context menu action selection. */
203
+ onContextMenuAction(action) {
204
+ const node = this.contextMenuNode;
205
+ this.closeContextMenu();
206
+ if (!node) {
207
+ return;
208
+ }
209
+ switch (action) {
210
+ case 'rename':
211
+ this.renameFileCategory = node;
212
+ break;
213
+ case 'delete':
214
+ this.deleteCategory(node);
215
+ break;
216
+ }
217
+ }
102
218
  async createNewCategory() {
103
219
  this.showNew = true;
104
220
  }
105
221
  cancelNewCategory() {
106
222
  this.showNew = false;
107
223
  }
108
- async handleDrop(e) {
109
- console.log(e);
110
- const sourceCategory = e.sourceItem.item.dataItem;
111
- const targetCategory = e.destinationItem.item.dataItem;
112
- sourceCategory.ParentID = targetCategory.ID;
113
- this.isLoading = true;
114
- await sourceCategory.Save();
115
- this.isLoading = false;
116
- }
117
224
  async saveNewCategory() {
118
225
  this.isLoading = true;
119
226
  const categoryEntity = await this.md.GetEntityObject('MJ: File Categories');
@@ -122,6 +229,7 @@ export class CategoryTreeComponent {
122
229
  await categoryEntity?.Save();
123
230
  this.categoriesData = [...this.categoriesData, categoryEntity];
124
231
  this.showNew = false;
232
+ this.newCategoryName = '';
125
233
  this.isLoading = false;
126
234
  }
127
235
  async deleteCategory(fileCategory) {
@@ -131,6 +239,7 @@ export class CategoryTreeComponent {
131
239
  if (!success) {
132
240
  console.error('Unable to delete file category:', fileCategory);
133
241
  this.sharedService.CreateSimpleNotification(`Unable to delete category '${fileCategory.Name}'`, 'error');
242
+ this.isLoading = false;
134
243
  return;
135
244
  }
136
245
  this.categoriesData = this.categoriesData.filter((c) => !UUIDsEqual(c.ID, ID));
@@ -138,22 +247,9 @@ export class CategoryTreeComponent {
138
247
  this.isLoading = false;
139
248
  }
140
249
  clearSelection() {
141
- this.selectedKeys = [];
250
+ this.selectedId = undefined;
142
251
  this.categorySelected.emit(undefined);
143
252
  }
144
- handleMenuSelect(e) {
145
- const action = e.item?.text?.toLowerCase() ?? '';
146
- switch (action) {
147
- case 'rename':
148
- this.renameFileCategory = e.item.data;
149
- break;
150
- case 'delete':
151
- this.deleteCategory(e.item.data);
152
- break;
153
- default:
154
- break;
155
- }
156
- }
157
253
  cancelRename() {
158
254
  this.renameFileCategory?.Revert();
159
255
  this.renameFileCategory = undefined;
@@ -180,36 +276,62 @@ export class CategoryTreeComponent {
180
276
  this.isLoading = false;
181
277
  }
182
278
  static ɵfac = function CategoryTreeComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || CategoryTreeComponent)(i0.ɵɵdirectiveInject(i1.SharedService)); };
183
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CategoryTreeComponent, selectors: [["mj-files-category-tree"]], outputs: { categorySelected: "categorySelected" }, standalone: false, decls: 10, vars: 4, consts: [["target", ""], ["templateForm", "ngForm"], [1, "container"], [1, "tree-header"], ["kendoButton", "", 3, "click"], ["kendoButton", "", "size", "small", 3, "click"], ["textField", "Name", "kendoTreeViewExpandable", "", "kendoTreeViewFlatDataBinding", "", "idField", "ID", "parentIdField", "ParentID", "kendoTreeViewDragAndDrop", "", "kendoTreeViewSelectable", "", 3, "selectionChange", "addItem", "nodes", "selectedKeys"], ["kendoTreeViewNodeTemplate", ""], ["title", "New file category", 3, "minWidth", "width"], ["title", "Rename file category", 3, "minWidth", "width"], [3, "select", "target"], ["text", "Rename", 3, "data"], ["text", "Delete", 3, "data", "disabled"], ["title", "New file category", 3, "close", "minWidth", "width"], ["text", "Name", 1, "k-form"], ["name", "name", "showErrorIcon", "initial", "required", "", 3, "ngModelChange", "ngModel", "showSuccessIcon", "disabled"], ["kendoButton", "", 3, "click", "disabled"], ["kendoButton", "", "themeColor", "primary", 3, "click", "disabled"], ["title", "Rename file category", 3, "close", "minWidth", "width"], ["name", "Name", "showErrorIcon", "initial", "required", "", 3, "ngModelChange", "ngModel", "showSuccessIcon", "disabled"]], template: function CategoryTreeComponent_Template(rf, ctx) { if (rf & 1) {
184
- i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "button", 4);
185
- i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_2_listener() { return ctx.clearSelection(); });
279
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: CategoryTreeComponent, selectors: [["mj-files-category-tree"]], outputs: { categorySelected: "categorySelected" }, standalone: false, decls: 26, vars: 9, consts: [["treeNode", ""], ["newForm", "ngForm"], ["renameForm", "ngForm"], [1, "container"], [1, "tree-header"], ["mjButton", "", 3, "click"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "category-tree"], [1, "context-menu", 3, "top", "left"], ["Title", "New file category", 3, "Close", "Visible", "Width", "MinWidth"], [1, "dialog-form-container"], [1, "mj-form-field"], [1, "mj-form-label"], ["name", "name", "required", "", 1, "mj-input", 3, "ngModelChange", "ngModel", "disabled"], ["mjButton", "", "variant", "primary", 3, "click", "disabled"], ["mjButton", "", 3, "click", "disabled"], ["Title", "Rename file category", 3, "Visible", "Width", "MinWidth"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"], [1, "tree-node"], [1, "tree-node-row", 3, "click", "contextmenu"], [1, "tree-expand-btn"], [1, "tree-expand-placeholder"], [1, "fa-solid", "fa-folder", "tree-folder-icon"], [1, "tree-node-label"], [1, "tree-expand-btn", 3, "click"], [1, "fa-solid"], [1, "context-menu", 3, "click"], [1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-pen"], ["disabled", "", 1, "context-menu-item", 3, "click"], [1, "fa-solid", "fa-trash-can"], ["Title", "Rename file category", 3, "Close", "Visible", "Width", "MinWidth"], ["name", "Name", "required", "", 1, "mj-input", 3, "ngModelChange", "ngModel", "disabled"]], template: function CategoryTreeComponent_Template(rf, ctx) { if (rf & 1) {
280
+ const _r1 = i0.ɵɵgetCurrentView();
281
+ i0.ɵɵelementStart(0, "div", 3)(1, "div", 4)(2, "button", 5);
282
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.clearSelection()); });
186
283
  i0.ɵɵtext(3, "All Files");
187
284
  i0.ɵɵelementEnd();
188
- i0.ɵɵelementStart(4, "button", 5);
189
- i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_4_listener() { return ctx.createNewCategory(); });
285
+ i0.ɵɵelementStart(4, "button", 6);
286
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.createNewCategory()); });
190
287
  i0.ɵɵtext(5, "Add category");
191
288
  i0.ɵɵelementEnd()();
192
- i0.ɵɵelementStart(6, "kendo-treeview", 6);
193
- i0.ɵɵlistener("selectionChange", function CategoryTreeComponent_Template_kendo_treeview_selectionChange_6_listener($event) { return ctx.categorySelected.emit($event.dataItem.ID); })("addItem", function CategoryTreeComponent_Template_kendo_treeview_addItem_6_listener($event) { return ctx.handleDrop($event); });
194
- i0.ɵɵtemplate(7, CategoryTreeComponent_ng_template_7_Template, 6, 5, "ng-template", 7);
289
+ i0.ɵɵelementStart(6, "div", 7);
290
+ i0.ɵɵrepeaterCreate(7, CategoryTreeComponent_For_8_Template, 1, 4, "ng-container", null, _forTrack0);
291
+ i0.ɵɵelementEnd();
292
+ i0.ɵɵtemplate(9, CategoryTreeComponent_ng_template_9_Template, 8, 7, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
293
+ i0.ɵɵconditionalCreate(11, CategoryTreeComponent_Conditional_11_Template, 7, 4, "div", 8);
294
+ i0.ɵɵelementStart(12, "mj-dialog", 9);
295
+ i0.ɵɵlistener("Close", function CategoryTreeComponent_Template_mj_dialog_Close_12_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.cancelNewCategory()); });
296
+ i0.ɵɵelementStart(13, "div", 10)(14, "form", null, 1)(16, "label", 11)(17, "span", 12);
297
+ i0.ɵɵtext(18, "Name");
195
298
  i0.ɵɵelementEnd();
196
- i0.ɵɵconditionalCreate(8, CategoryTreeComponent_Conditional_8_Template, 11, 7, "kendo-dialog", 8);
197
- i0.ɵɵconditionalCreate(9, CategoryTreeComponent_Conditional_9_Template, 11, 7, "kendo-dialog", 9);
299
+ i0.ɵɵelementStart(19, "input", 13);
300
+ i0.ɵɵtwoWayListener("ngModelChange", function CategoryTreeComponent_Template_input_ngModelChange_19_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.newCategoryName, $event) || (ctx.newCategoryName = $event); return i0.ɵɵresetView($event); });
301
+ i0.ɵɵelementEnd()()()();
302
+ i0.ɵɵelementStart(20, "mj-dialog-actions")(21, "button", 14);
303
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_21_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.saveNewCategory()); });
304
+ i0.ɵɵtext(22, "Save");
305
+ i0.ɵɵelementEnd();
306
+ i0.ɵɵelementStart(23, "button", 15);
307
+ i0.ɵɵlistener("click", function CategoryTreeComponent_Template_button_click_23_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.cancelNewCategory()); });
308
+ i0.ɵɵtext(24, "Cancel");
309
+ i0.ɵɵelementEnd()()();
310
+ i0.ɵɵconditionalCreate(25, CategoryTreeComponent_Conditional_25_Template, 13, 7, "mj-dialog", 16);
198
311
  i0.ɵɵelementEnd();
199
312
  } if (rf & 2) {
200
- i0.ɵɵadvance(6);
201
- i0.ɵɵproperty("nodes", ctx.categoriesData)("selectedKeys", ctx.selectedKeys);
202
- i0.ɵɵadvance(2);
203
- i0.ɵɵconditional(ctx.showNew ? 8 : -1);
313
+ i0.ɵɵadvance(7);
314
+ i0.ɵɵrepeater(ctx.rootNodes);
315
+ i0.ɵɵadvance(4);
316
+ i0.ɵɵconditional(ctx.contextMenuVisible ? 11 : -1);
204
317
  i0.ɵɵadvance();
205
- i0.ɵɵconditional(!!ctx.renameFileCategory ? 9 : -1);
206
- } }, dependencies: [i2.TreeViewComponent, i2.NodeTemplateDirective, i2.ExpandDirective, i2.SelectDirective, i2.FlatDataBindingDirective, i2.DragAndDropDirective, i3.ɵNgNoValidate, i3.NgControlStatus, i3.NgControlStatusGroup, i3.RequiredValidator, i3.NgModel, i3.NgForm, i4.DialogComponent, i4.DialogActionsComponent, i5.ButtonComponent, i6.LabelComponent, i7.TextBoxComponent, i8.MenuItemComponent, i8.ContextMenuComponent], styles: ["\n\n.container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n}\n\nkendo-treeview[_ngcontent-%COMP%] {\n width: 250px;\n}\n\n.tree-header[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n}\n\nkendo-label[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n column-gap: 12px;\n margin-bottom: 4px;\n}\n\nkendo-label[_ngcontent-%COMP%] kendo-textbox[_ngcontent-%COMP%] {\n width: 100%;\n}"] });
318
+ i0.ɵɵproperty("Visible", ctx.showNew)("Width", 450)("MinWidth", 250);
319
+ i0.ɵɵadvance(7);
320
+ i0.ɵɵtwoWayProperty("ngModel", ctx.newCategoryName);
321
+ i0.ɵɵproperty("disabled", ctx.isLoading);
322
+ i0.ɵɵadvance(2);
323
+ i0.ɵɵproperty("disabled", ctx.isLoading);
324
+ i0.ɵɵadvance(2);
325
+ i0.ɵɵproperty("disabled", ctx.isLoading);
326
+ i0.ɵɵadvance(2);
327
+ i0.ɵɵconditional(!!ctx.renameFileCategory ? 25 : -1);
328
+ } }, dependencies: [i2.NgTemplateOutlet, i3.ɵNgNoValidate, i3.DefaultValueAccessor, i3.NgControlStatus, i3.NgControlStatusGroup, i3.RequiredValidator, i3.NgModel, i3.NgForm, i4.MJButtonDirective, i4.MJDialogComponent, i4.MJDialogActionsComponent], styles: ["\n\n.container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n}\n\n.category-tree[_ngcontent-%COMP%] {\n width: 250px;\n max-height: 500px;\n overflow-y: auto;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n}\n\n.tree-header[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n margin-bottom: 4px;\n}\n\n.tree-node-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.15s;\n user-select: none;\n}\n\n.tree-node-row[_ngcontent-%COMP%]:hover {\n background-color: var(--mj-bg-surface-hover);\n}\n\n.tree-node-row.selected[_ngcontent-%COMP%] {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.tree-expand-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n.tree-expand-placeholder[_ngcontent-%COMP%] {\n width: 16px;\n height: 16px;\n display: inline-block;\n}\n\n.tree-folder-icon[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 14px;\n}\n\n.tree-node-label[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n\n\n.context-menu[_ngcontent-%COMP%] {\n position: fixed;\n z-index: 10000;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px var(--mj-bg-overlay);\n padding: 4px 0;\n min-width: 140px;\n}\n\n.context-menu-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n background: none;\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n text-align: left;\n}\n\n.context-menu-item[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu-item[_ngcontent-%COMP%]:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.dialog-form-container[_ngcontent-%COMP%] {\n padding: 8px 0;\n}\n\n.mj-form-field[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: row;\n align-items: center;\n column-gap: 12px;\n margin-bottom: 8px;\n}\n\n.mj-form-label[_ngcontent-%COMP%] {\n min-width: 80px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.mj-form-field[_ngcontent-%COMP%] .mj-input[_ngcontent-%COMP%] {\n width: 100%;\n}"] });
207
329
  }
208
330
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(CategoryTreeComponent, [{
209
331
  type: Component,
210
- args: [{ standalone: false, selector: 'mj-files-category-tree', template: "<div class=\"container\">\n <div class=\"tree-header\">\n <button kendoButton (click)=\"clearSelection()\">All Files</button>\n <button kendoButton (click)=\"createNewCategory()\" size=\"small\">Add category</button>\n </div>\n <kendo-treeview\n [nodes]=\"categoriesData\"\n textField=\"Name\"\n kendoTreeViewExpandable\n kendoTreeViewFlatDataBinding\n idField=\"ID\"\n parentIdField=\"ParentID\"\n (selectionChange)=\"categorySelected.emit($event.dataItem.ID)\"\n kendoTreeViewDragAndDrop\n kendoTreeViewSelectable\n (addItem)=\"handleDrop($event)\"\n [selectedKeys]=\"selectedKeys\"\n >\n <ng-template kendoTreeViewNodeTemplate let-dataItem>\n <span #target>{{ dataItem.Name }} </span>\n <kendo-contextmenu [target]=\"target\" (select)=\"handleMenuSelect($event)\">\n <kendo-menu-item text=\"Rename\" [data]=\"dataItem\"> </kendo-menu-item>\n <kendo-menu-item text=\"Delete\" [data]=\"dataItem\" [disabled]=\"true\"> </kendo-menu-item>\n </kendo-contextmenu>\n </ng-template>\n </kendo-treeview>\n\n @if (showNew) {\n <kendo-dialog title=\"New file category\" (close)=\"cancelNewCategory()\" [minWidth]=\"250\" [width]=\"450\">\n <div class=\"container\">\n <form #templateForm=\"ngForm\">\n <kendo-label class=\"k-form\" text=\"Name\">\n <kendo-textbox\n name=\"name\"\n [(ngModel)]=\"newCategoryName\"\n [showSuccessIcon]=\"newCategoryName.length > 0\"\n showErrorIcon=\"initial\"\n required\n [disabled]=\"isLoading\"\n ></kendo-textbox>\n </kendo-label>\n </form>\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelNewCategory()\" [disabled]=\"isLoading\">Cancel</button>\n <button kendoButton (click)=\"saveNewCategory()\" [disabled]=\"isLoading\" themeColor=\"primary\">Save</button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n\n @if (!!renameFileCategory) {\n <kendo-dialog title=\"Rename file category\" (close)=\"cancelRename()\" [minWidth]=\"250\" [width]=\"450\">\n <div class=\"container\">\n <form #templateForm=\"ngForm\">\n <kendo-label class=\"k-form\" text=\"Name\">\n <kendo-textbox\n name=\"Name\"\n [(ngModel)]=\"renameFileCategory.Name\"\n [showSuccessIcon]=\"renameFileCategory.Name.length > 0\"\n showErrorIcon=\"initial\"\n required\n [disabled]=\"isLoading\"\n ></kendo-textbox>\n </kendo-label>\n </form>\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelRename()\" [disabled]=\"isLoading\">Cancel</button>\n <button kendoButton (click)=\"saveRename()\" [disabled]=\"isLoading || !renameFileCategory.Dirty\" themeColor=\"primary\">Save</button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n</div>\n", styles: ["/* Styles for category tree */\n.container {\n display: flex;\n flex-direction: column;\n}\n\nkendo-treeview {\n width: 250px;\n}\n\n.tree-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n}\n\nkendo-label {\n display: flex;\n flex-direction: row;\n align-items: center;\n column-gap: 12px;\n margin-bottom: 4px;\n}\n\nkendo-label kendo-textbox {\n width: 100%;\n}"] }]
332
+ args: [{ standalone: false, selector: 'mj-files-category-tree', template: "<div class=\"container\">\n <div class=\"tree-header\">\n <button mjButton (click)=\"clearSelection()\">All Files</button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewCategory()\">Add category</button>\n </div>\n\n <div class=\"category-tree\">\n @for (node of rootNodes; track node.ID) {\n <ng-container *ngTemplateOutlet=\"treeNode; context: { $implicit: node, level: 0 }\"></ng-container>\n }\n </div>\n\n <ng-template #treeNode let-node let-level=\"level\">\n <div class=\"tree-node\" [style.padding-left.px]=\"level * 20\">\n <div class=\"tree-node-row\"\n [class.selected]=\"isSelected(node)\"\n (click)=\"selectNode(node)\"\n (contextmenu)=\"onContextMenu($event, node)\">\n @if (hasChildren(node)) {\n <button class=\"tree-expand-btn\" (click)=\"toggleExpand(node, $event)\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!isExpanded(node)\" [class.fa-chevron-down]=\"isExpanded(node)\"></i>\n </button>\n } @else {\n <span class=\"tree-expand-placeholder\"></span>\n }\n <i class=\"fa-solid fa-folder tree-folder-icon\"></i>\n <span class=\"tree-node-label\">{{ node.Name }}</span>\n </div>\n @if (isExpanded(node)) {\n @for (child of getChildren(node); track child.ID) {\n <ng-container *ngTemplateOutlet=\"treeNode; context: { $implicit: child, level: level + 1 }\"></ng-container>\n }\n }\n </div>\n </ng-template>\n\n <!-- Context Menu -->\n @if (contextMenuVisible) {\n <div class=\"context-menu\"\n [style.top.px]=\"contextMenuY\"\n [style.left.px]=\"contextMenuX\"\n (document:click)=\"closeContextMenu()\">\n <button class=\"context-menu-item\" (click)=\"onContextMenuAction('rename')\">\n <i class=\"fa-solid fa-pen\"></i> Rename\n </button>\n <button class=\"context-menu-item\" (click)=\"onContextMenuAction('delete')\" disabled>\n <i class=\"fa-solid fa-trash-can\"></i> Delete\n </button>\n </div>\n }\n\n <mj-dialog\n [Visible]=\"showNew\"\n Title=\"New file category\"\n [Width]=\"450\"\n [MinWidth]=\"250\"\n (Close)=\"cancelNewCategory()\">\n <div class=\"dialog-form-container\">\n <form #newForm=\"ngForm\">\n <label class=\"mj-form-field\">\n <span class=\"mj-form-label\">Name</span>\n <input\n class=\"mj-input\"\n name=\"name\"\n [(ngModel)]=\"newCategoryName\"\n required\n [disabled]=\"isLoading\" />\n </label>\n </form>\n </div>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"saveNewCategory()\" [disabled]=\"isLoading\">Save</button>\n <button mjButton (click)=\"cancelNewCategory()\" [disabled]=\"isLoading\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n\n @if (!!renameFileCategory) {\n <mj-dialog\n [Visible]=\"true\"\n Title=\"Rename file category\"\n [Width]=\"450\"\n [MinWidth]=\"250\"\n (Close)=\"cancelRename()\">\n <div class=\"dialog-form-container\">\n <form #renameForm=\"ngForm\">\n <label class=\"mj-form-field\">\n <span class=\"mj-form-label\">Name</span>\n <input\n class=\"mj-input\"\n name=\"Name\"\n [(ngModel)]=\"renameFileCategory.Name\"\n required\n [disabled]=\"isLoading\" />\n </label>\n </form>\n </div>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"saveRename()\" [disabled]=\"isLoading || !renameFileCategory.Dirty\">Save</button>\n <button mjButton (click)=\"cancelRename()\" [disabled]=\"isLoading\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n }\n</div>\n", styles: ["/* Styles for category tree */\n.container {\n display: flex;\n flex-direction: column;\n}\n\n.category-tree {\n width: 250px;\n max-height: 500px;\n overflow-y: auto;\n border: 1px solid var(--mj-border-subtle);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n}\n\n.tree-header {\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n margin-bottom: 4px;\n}\n\n.tree-node-row {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.15s;\n user-select: none;\n}\n\n.tree-node-row:hover {\n background-color: var(--mj-bg-surface-hover);\n}\n\n.tree-node-row.selected {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.tree-expand-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n.tree-expand-placeholder {\n width: 16px;\n height: 16px;\n display: inline-block;\n}\n\n.tree-folder-icon {\n color: var(--mj-text-muted);\n font-size: 14px;\n}\n\n.tree-node-label {\n font-size: 13px;\n color: var(--mj-text-primary);\n}\n\n/* Context Menu */\n.context-menu {\n position: fixed;\n z-index: 10000;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 12px var(--mj-bg-overlay);\n padding: 4px 0;\n min-width: 140px;\n}\n\n.context-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n background: none;\n color: var(--mj-text-primary);\n font-size: 13px;\n cursor: pointer;\n text-align: left;\n}\n\n.context-menu-item:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n}\n\n.context-menu-item:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.dialog-form-container {\n padding: 8px 0;\n}\n\n.mj-form-field {\n display: flex;\n flex-direction: row;\n align-items: center;\n column-gap: 12px;\n margin-bottom: 8px;\n}\n\n.mj-form-label {\n min-width: 80px;\n color: var(--mj-text-secondary);\n font-weight: 500;\n}\n\n.mj-form-field .mj-input {\n width: 100%;\n}\n"] }]
211
333
  }], () => [{ type: i1.SharedService }], { categorySelected: [{
212
334
  type: Output
213
335
  }] }); })();
214
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CategoryTreeComponent, { className: "CategoryTreeComponent", filePath: "src/lib/category-tree/category-tree.ts", lineNumber: 16 }); })();
336
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(CategoryTreeComponent, { className: "CategoryTreeComponent", filePath: "src/lib/category-tree/category-tree.ts", lineNumber: 13 }); })();
215
337
  //# sourceMappingURL=category-tree.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"category-tree.js","sourceRoot":"","sources":["../../../src/lib/category-tree/category-tree.ts","../../../src/lib/category-tree/category-tree.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, OnInit, Output } from '@angular/core';\nimport { Metadata, RunView } from '@memberjunction/core';\nimport { MJFileCategoryEntity } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\n\nimport { ContextMenuSelectEvent } from '@progress/kendo-angular-menu';\nimport { TreeItemAddRemoveArgs } from '@progress/kendo-angular-treeview';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n@Component({\n standalone: false,\n selector: 'mj-files-category-tree',\n templateUrl: './category-tree.html',\n styleUrls: ['./category-tree.css'],\n})\nexport class CategoryTreeComponent implements OnInit {\n @Output() categorySelected = new EventEmitter<string | undefined>();\n\n public isLoading: boolean = false;\n public showNew: boolean = false;\n public newCategoryName = '';\n public selectedKeys = [];\n public renameFileCategory: MJFileCategoryEntity | undefined;\n\n public categoriesData: MJFileCategoryEntity[] = [];\n\n private md = new Metadata();\n\n constructor(private sharedService: SharedService) {}\n\n ngOnInit(): void {\n this.Refresh();\n }\n\n async createNewCategory() {\n this.showNew = true;\n }\n\n cancelNewCategory() {\n this.showNew = false;\n }\n\n async handleDrop(e: TreeItemAddRemoveArgs) {\n console.log(e);\n const sourceCategory: MJFileCategoryEntity = e.sourceItem.item.dataItem;\n const targetCategory: MJFileCategoryEntity = e.destinationItem.item.dataItem;\n sourceCategory.ParentID = targetCategory.ID;\n\n this.isLoading = true;\n await sourceCategory.Save();\n this.isLoading = false;\n }\n\n async saveNewCategory() {\n this.isLoading = true;\n const categoryEntity: MJFileCategoryEntity = await this.md.GetEntityObject('MJ: File Categories');\n categoryEntity.NewRecord();\n categoryEntity.Name = this.newCategoryName;\n await categoryEntity?.Save();\n this.categoriesData = [...this.categoriesData, categoryEntity];\n this.showNew = false;\n this.isLoading = false;\n }\n\n async deleteCategory(fileCategory: MJFileCategoryEntity) {\n this.isLoading = true;\n const { ID } = fileCategory;\n const success = await fileCategory.Delete();\n if (!success) {\n console.error('Unable to delete file category:', fileCategory);\n this.sharedService.CreateSimpleNotification(`Unable to delete category '${fileCategory.Name}'`, 'error');\n return;\n }\n\n this.categoriesData = this.categoriesData.filter((c) => !UUIDsEqual(c.ID, ID));\n this.clearSelection();\n this.isLoading = false;\n }\n\n clearSelection() {\n this.selectedKeys = [];\n this.categorySelected.emit(undefined);\n }\n\n handleMenuSelect(e: ContextMenuSelectEvent) {\n const action = e.item?.text?.toLowerCase() ?? '';\n switch (action) {\n case 'rename':\n this.renameFileCategory = e.item.data;\n break;\n\n case 'delete':\n this.deleteCategory(e.item.data);\n break;\n\n default:\n break;\n }\n }\n\n cancelRename() {\n this.renameFileCategory?.Revert();\n this.renameFileCategory = undefined;\n }\n\n async saveRename() {\n this.isLoading = true;\n await this.renameFileCategory?.Save();\n this.renameFileCategory = undefined;\n this.isLoading = false;\n }\n\n async Refresh() {\n this.isLoading = true;\n\n const rv = new RunView();\n const result = await rv.RunView({\n EntityName: 'MJ: File Categories',\n ResultType: 'entity_object',\n });\n\n if (result.Success) {\n this.categoriesData = <MJFileCategoryEntity[]>result.Results;\n } else {\n throw new Error('Error loading file categories: ' + result.ErrorMessage);\n }\n this.isLoading = false;\n }\n}\n","<div class=\"container\">\n <div class=\"tree-header\">\n <button kendoButton (click)=\"clearSelection()\">All Files</button>\n <button kendoButton (click)=\"createNewCategory()\" size=\"small\">Add category</button>\n </div>\n <kendo-treeview\n [nodes]=\"categoriesData\"\n textField=\"Name\"\n kendoTreeViewExpandable\n kendoTreeViewFlatDataBinding\n idField=\"ID\"\n parentIdField=\"ParentID\"\n (selectionChange)=\"categorySelected.emit($event.dataItem.ID)\"\n kendoTreeViewDragAndDrop\n kendoTreeViewSelectable\n (addItem)=\"handleDrop($event)\"\n [selectedKeys]=\"selectedKeys\"\n >\n <ng-template kendoTreeViewNodeTemplate let-dataItem>\n <span #target>{{ dataItem.Name }} </span>\n <kendo-contextmenu [target]=\"target\" (select)=\"handleMenuSelect($event)\">\n <kendo-menu-item text=\"Rename\" [data]=\"dataItem\"> </kendo-menu-item>\n <kendo-menu-item text=\"Delete\" [data]=\"dataItem\" [disabled]=\"true\"> </kendo-menu-item>\n </kendo-contextmenu>\n </ng-template>\n </kendo-treeview>\n\n @if (showNew) {\n <kendo-dialog title=\"New file category\" (close)=\"cancelNewCategory()\" [minWidth]=\"250\" [width]=\"450\">\n <div class=\"container\">\n <form #templateForm=\"ngForm\">\n <kendo-label class=\"k-form\" text=\"Name\">\n <kendo-textbox\n name=\"name\"\n [(ngModel)]=\"newCategoryName\"\n [showSuccessIcon]=\"newCategoryName.length > 0\"\n showErrorIcon=\"initial\"\n required\n [disabled]=\"isLoading\"\n ></kendo-textbox>\n </kendo-label>\n </form>\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelNewCategory()\" [disabled]=\"isLoading\">Cancel</button>\n <button kendoButton (click)=\"saveNewCategory()\" [disabled]=\"isLoading\" themeColor=\"primary\">Save</button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n\n @if (!!renameFileCategory) {\n <kendo-dialog title=\"Rename file category\" (close)=\"cancelRename()\" [minWidth]=\"250\" [width]=\"450\">\n <div class=\"container\">\n <form #templateForm=\"ngForm\">\n <kendo-label class=\"k-form\" text=\"Name\">\n <kendo-textbox\n name=\"Name\"\n [(ngModel)]=\"renameFileCategory.Name\"\n [showSuccessIcon]=\"renameFileCategory.Name.length > 0\"\n showErrorIcon=\"initial\"\n required\n [disabled]=\"isLoading\"\n ></kendo-textbox>\n </kendo-label>\n </form>\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelRename()\" [disabled]=\"isLoading\">Cancel</button>\n <button kendoButton (click)=\"saveRename()\" [disabled]=\"isLoading || !renameFileCategory.Dirty\" themeColor=\"primary\">Save</button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n</div>\n"]}
1
+ {"version":3,"file":"category-tree.js","sourceRoot":"","sources":["../../../src/lib/category-tree/category-tree.ts","../../../src/lib/category-tree/category-tree.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, OnInit, Output } from '@angular/core';\nimport { Metadata, RunView } from '@memberjunction/core';\nimport { MJFileCategoryEntity } from '@memberjunction/core-entities';\nimport { SharedService } from '@memberjunction/ng-shared';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n@Component({\n standalone: false,\n selector: 'mj-files-category-tree',\n templateUrl: './category-tree.html',\n styleUrls: ['./category-tree.css'],\n})\nexport class CategoryTreeComponent implements OnInit {\n @Output() categorySelected = new EventEmitter<string | undefined>();\n\n public isLoading: boolean = false;\n public showNew: boolean = false;\n public newCategoryName = '';\n public renameFileCategory: MJFileCategoryEntity | undefined;\n\n public categoriesData: MJFileCategoryEntity[] = [];\n\n /** Expanded node IDs for the tree */\n public expandedIds = new Set<string>();\n\n /** Currently selected node ID */\n private selectedId: string | undefined;\n\n /** Context menu state */\n public contextMenuVisible = false;\n public contextMenuX = 0;\n public contextMenuY = 0;\n private contextMenuNode: MJFileCategoryEntity | undefined;\n\n private md = new Metadata();\n\n constructor(private sharedService: SharedService) {}\n\n ngOnInit(): void {\n this.Refresh();\n }\n\n /** Returns root-level nodes (no parent). */\n get rootNodes(): MJFileCategoryEntity[] {\n return this.categoriesData.filter((c) => !c.ParentID);\n }\n\n /** Checks if a node has children. */\n hasChildren(node: MJFileCategoryEntity): boolean {\n return this.categoriesData.some((c) => UUIDsEqual(c.ParentID, node.ID));\n }\n\n /** Returns children of a node. */\n getChildren(node: MJFileCategoryEntity): MJFileCategoryEntity[] {\n return this.categoriesData.filter((c) => UUIDsEqual(c.ParentID, node.ID));\n }\n\n /** Checks if a node is expanded. */\n isExpanded(node: MJFileCategoryEntity): boolean {\n return this.expandedIds.has(node.ID);\n }\n\n /** Toggles expand/collapse on a node. */\n toggleExpand(node: MJFileCategoryEntity, event: Event): void {\n event.stopPropagation();\n if (this.expandedIds.has(node.ID)) {\n this.expandedIds.delete(node.ID);\n } else {\n this.expandedIds.add(node.ID);\n }\n }\n\n /** Checks if a node is the currently selected node. */\n isSelected(node: MJFileCategoryEntity): boolean {\n return this.selectedId != null && UUIDsEqual(this.selectedId, node.ID);\n }\n\n /** Selects a node and emits event. */\n selectNode(node: MJFileCategoryEntity): void {\n this.selectedId = node.ID;\n this.categorySelected.emit(node.ID);\n }\n\n /** Opens context menu on right-click. */\n onContextMenu(event: MouseEvent, node: MJFileCategoryEntity): void {\n event.preventDefault();\n event.stopPropagation();\n this.contextMenuNode = node;\n this.contextMenuX = event.clientX;\n this.contextMenuY = event.clientY;\n this.contextMenuVisible = true;\n }\n\n /** Closes the context menu. */\n closeContextMenu(): void {\n this.contextMenuVisible = false;\n this.contextMenuNode = undefined;\n }\n\n /** Handles context menu action selection. */\n onContextMenuAction(action: string): void {\n const node = this.contextMenuNode;\n this.closeContextMenu();\n if (!node) {\n return;\n }\n\n switch (action) {\n case 'rename':\n this.renameFileCategory = node;\n break;\n case 'delete':\n this.deleteCategory(node);\n break;\n }\n }\n\n async createNewCategory() {\n this.showNew = true;\n }\n\n cancelNewCategory() {\n this.showNew = false;\n }\n\n async saveNewCategory() {\n this.isLoading = true;\n const categoryEntity: MJFileCategoryEntity = await this.md.GetEntityObject('MJ: File Categories');\n categoryEntity.NewRecord();\n categoryEntity.Name = this.newCategoryName;\n await categoryEntity?.Save();\n this.categoriesData = [...this.categoriesData, categoryEntity];\n this.showNew = false;\n this.newCategoryName = '';\n this.isLoading = false;\n }\n\n async deleteCategory(fileCategory: MJFileCategoryEntity) {\n this.isLoading = true;\n const { ID } = fileCategory;\n const success = await fileCategory.Delete();\n if (!success) {\n console.error('Unable to delete file category:', fileCategory);\n this.sharedService.CreateSimpleNotification(`Unable to delete category '${fileCategory.Name}'`, 'error');\n this.isLoading = false;\n return;\n }\n\n this.categoriesData = this.categoriesData.filter((c) => !UUIDsEqual(c.ID, ID));\n this.clearSelection();\n this.isLoading = false;\n }\n\n clearSelection() {\n this.selectedId = undefined;\n this.categorySelected.emit(undefined);\n }\n\n cancelRename() {\n this.renameFileCategory?.Revert();\n this.renameFileCategory = undefined;\n }\n\n async saveRename() {\n this.isLoading = true;\n await this.renameFileCategory?.Save();\n this.renameFileCategory = undefined;\n this.isLoading = false;\n }\n\n async Refresh() {\n this.isLoading = true;\n\n const rv = new RunView();\n const result = await rv.RunView({\n EntityName: 'MJ: File Categories',\n ResultType: 'entity_object',\n });\n\n if (result.Success) {\n this.categoriesData = <MJFileCategoryEntity[]>result.Results;\n } else {\n throw new Error('Error loading file categories: ' + result.ErrorMessage);\n }\n this.isLoading = false;\n }\n}\n","<div class=\"container\">\n <div class=\"tree-header\">\n <button mjButton (click)=\"clearSelection()\">All Files</button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"createNewCategory()\">Add category</button>\n </div>\n\n <div class=\"category-tree\">\n @for (node of rootNodes; track node.ID) {\n <ng-container *ngTemplateOutlet=\"treeNode; context: { $implicit: node, level: 0 }\"></ng-container>\n }\n </div>\n\n <ng-template #treeNode let-node let-level=\"level\">\n <div class=\"tree-node\" [style.padding-left.px]=\"level * 20\">\n <div class=\"tree-node-row\"\n [class.selected]=\"isSelected(node)\"\n (click)=\"selectNode(node)\"\n (contextmenu)=\"onContextMenu($event, node)\">\n @if (hasChildren(node)) {\n <button class=\"tree-expand-btn\" (click)=\"toggleExpand(node, $event)\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!isExpanded(node)\" [class.fa-chevron-down]=\"isExpanded(node)\"></i>\n </button>\n } @else {\n <span class=\"tree-expand-placeholder\"></span>\n }\n <i class=\"fa-solid fa-folder tree-folder-icon\"></i>\n <span class=\"tree-node-label\">{{ node.Name }}</span>\n </div>\n @if (isExpanded(node)) {\n @for (child of getChildren(node); track child.ID) {\n <ng-container *ngTemplateOutlet=\"treeNode; context: { $implicit: child, level: level + 1 }\"></ng-container>\n }\n }\n </div>\n </ng-template>\n\n <!-- Context Menu -->\n @if (contextMenuVisible) {\n <div class=\"context-menu\"\n [style.top.px]=\"contextMenuY\"\n [style.left.px]=\"contextMenuX\"\n (document:click)=\"closeContextMenu()\">\n <button class=\"context-menu-item\" (click)=\"onContextMenuAction('rename')\">\n <i class=\"fa-solid fa-pen\"></i> Rename\n </button>\n <button class=\"context-menu-item\" (click)=\"onContextMenuAction('delete')\" disabled>\n <i class=\"fa-solid fa-trash-can\"></i> Delete\n </button>\n </div>\n }\n\n <mj-dialog\n [Visible]=\"showNew\"\n Title=\"New file category\"\n [Width]=\"450\"\n [MinWidth]=\"250\"\n (Close)=\"cancelNewCategory()\">\n <div class=\"dialog-form-container\">\n <form #newForm=\"ngForm\">\n <label class=\"mj-form-field\">\n <span class=\"mj-form-label\">Name</span>\n <input\n class=\"mj-input\"\n name=\"name\"\n [(ngModel)]=\"newCategoryName\"\n required\n [disabled]=\"isLoading\" />\n </label>\n </form>\n </div>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"saveNewCategory()\" [disabled]=\"isLoading\">Save</button>\n <button mjButton (click)=\"cancelNewCategory()\" [disabled]=\"isLoading\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n\n @if (!!renameFileCategory) {\n <mj-dialog\n [Visible]=\"true\"\n Title=\"Rename file category\"\n [Width]=\"450\"\n [MinWidth]=\"250\"\n (Close)=\"cancelRename()\">\n <div class=\"dialog-form-container\">\n <form #renameForm=\"ngForm\">\n <label class=\"mj-form-field\">\n <span class=\"mj-form-label\">Name</span>\n <input\n class=\"mj-input\"\n name=\"Name\"\n [(ngModel)]=\"renameFileCategory.Name\"\n required\n [disabled]=\"isLoading\" />\n </label>\n </form>\n </div>\n <mj-dialog-actions>\n <button mjButton variant=\"primary\" (click)=\"saveRename()\" [disabled]=\"isLoading || !renameFileCategory.Dirty\">Save</button>\n <button mjButton (click)=\"cancelRename()\" [disabled]=\"isLoading\">Cancel</button>\n </mj-dialog-actions>\n </mj-dialog>\n }\n</div>\n"]}