@acorex/modules 20.5.0-next.0 → 20.5.0-next.2
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.
- package/common/index.d.ts +11 -17
- package/fesm2022/{acorex-modules-application-management-acorex-modules-application-management-CQMnhxLa.mjs → acorex-modules-application-management-acorex-modules-application-management-BqYLpEvY.mjs} +23 -41
- package/fesm2022/acorex-modules-application-management-acorex-modules-application-management-BqYLpEvY.mjs.map +1 -0
- package/fesm2022/acorex-modules-application-management-menu-list.component-D4uW0aXY.mjs +830 -0
- package/fesm2022/acorex-modules-application-management-menu-list.component-D4uW0aXY.mjs.map +1 -0
- package/fesm2022/acorex-modules-application-management.mjs +1 -1
- package/fesm2022/acorex-modules-common.mjs +429 -559
- package/fesm2022/acorex-modules-common.mjs.map +1 -1
- package/fesm2022/acorex-modules-data-management.mjs +0 -2
- package/fesm2022/acorex-modules-data-management.mjs.map +1 -1
- package/fesm2022/acorex-modules-report-management.mjs +3 -6
- package/fesm2022/acorex-modules-report-management.mjs.map +1 -1
- package/package.json +14 -14
- package/fesm2022/acorex-modules-application-management-acorex-modules-application-management-CQMnhxLa.mjs.map +0 -1
- package/fesm2022/acorex-modules-application-management-menu-list.component-BMbl5rtn.mjs +0 -1046
- package/fesm2022/acorex-modules-application-management-menu-list.component-BMbl5rtn.mjs.map +0 -1
|
@@ -1,1046 +0,0 @@
|
|
|
1
|
-
import { CdkDrag, CdkDragHandle, CdkDropList, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
|
|
2
|
-
import * as i4 from '@angular/common';
|
|
3
|
-
import { CommonModule } from '@angular/common';
|
|
4
|
-
import * as i0 from '@angular/core';
|
|
5
|
-
import { input, output, signal, ViewEncapsulation, ChangeDetectionStrategy, Component, inject, Injectable } from '@angular/core';
|
|
6
|
-
import { ActivatedRoute } from '@angular/router';
|
|
7
|
-
import * as i1 from '@acorex/components/button';
|
|
8
|
-
import { AXButtonModule } from '@acorex/components/button';
|
|
9
|
-
import { AXDialogService } from '@acorex/components/dialog';
|
|
10
|
-
import { AXToastService } from '@acorex/components/toast';
|
|
11
|
-
import * as i5 from '@acorex/core/translation';
|
|
12
|
-
import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
|
|
13
|
-
import { AXPPlatformScope } from '@acorex/platform/core';
|
|
14
|
-
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
15
|
-
import { AXPThemeLayoutBlockComponent, AXPStateMessageComponent } from '@acorex/platform/layout/components';
|
|
16
|
-
import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex/platform/layout/views';
|
|
17
|
-
import * as i2 from '@acorex/components/decorators';
|
|
18
|
-
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
19
|
-
import * as i3 from '@acorex/components/dropdown';
|
|
20
|
-
import { AXDropdownModule } from '@acorex/components/dropdown';
|
|
21
|
-
import { AXPSettingService, AXPMenuProviderService, AXPMenuService } from '@acorex/platform/common';
|
|
22
|
-
import { cloneDeep, sortBy } from 'lodash-es';
|
|
23
|
-
import { A as AXP_MENU_CUSTOMIZATION_KEY, a as AXP_MENU_CUSTOMIZATION_DEFAULT } from './acorex-modules-application-management-acorex-modules-application-management-CQMnhxLa.mjs';
|
|
24
|
-
|
|
25
|
-
//#region ---- Menu Tree Item Component ----
|
|
26
|
-
class AXMMenuTreeItemComponent {
|
|
27
|
-
constructor() {
|
|
28
|
-
//#region ---- Inputs ----
|
|
29
|
-
this.item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
30
|
-
this.level = input(0, ...(ngDevMode ? [{ debugName: "level" }] : []));
|
|
31
|
-
this.canDelete = input(false, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
|
|
32
|
-
this.connectedDropLists = input([], ...(ngDevMode ? [{ debugName: "connectedDropLists" }] : []));
|
|
33
|
-
this.canMoveUp = input(false, ...(ngDevMode ? [{ debugName: "canMoveUp" }] : []));
|
|
34
|
-
this.canMoveDown = input(false, ...(ngDevMode ? [{ debugName: "canMoveDown" }] : []));
|
|
35
|
-
this.canMoveToParent = input(false, ...(ngDevMode ? [{ debugName: "canMoveToParent" }] : []));
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region ---- Outputs ----
|
|
38
|
-
this.actionTriggered = output();
|
|
39
|
-
this.itemDropped = output();
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region ---- Component State ----
|
|
42
|
-
this.isExpanded = signal(true, ...(ngDevMode ? [{ debugName: "isExpanded" }] : []));
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region ---- Helper Methods ----
|
|
46
|
-
/**
|
|
47
|
-
* Get unique drop list ID for this item's children
|
|
48
|
-
*/
|
|
49
|
-
getDropListId() {
|
|
50
|
-
const item = this.item();
|
|
51
|
-
return `menu-${item.name || item.path || 'unnamed'}`;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Get children array, ensuring it's always initialized
|
|
55
|
-
* This allows drop zones to exist even for items with no children yet
|
|
56
|
-
*/
|
|
57
|
-
getChildrenArray() {
|
|
58
|
-
const item = this.item();
|
|
59
|
-
if (!item.children) {
|
|
60
|
-
// Initialize children array if it doesn't exist
|
|
61
|
-
item.children = [];
|
|
62
|
-
}
|
|
63
|
-
return item.children;
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
//#region ---- Event Handlers ----
|
|
67
|
-
/**
|
|
68
|
-
* Toggle expand/collapse
|
|
69
|
-
*/
|
|
70
|
-
toggleExpand() {
|
|
71
|
-
this.isExpanded.update((expanded) => !expanded);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Handle action trigger
|
|
75
|
-
*/
|
|
76
|
-
onAction(action) {
|
|
77
|
-
this.actionTriggered.emit({ action, item: this.item() });
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Handle drop event
|
|
81
|
-
*/
|
|
82
|
-
onDrop(event) {
|
|
83
|
-
this.itemDropped.emit(event);
|
|
84
|
-
}
|
|
85
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
86
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: AXMMenuTreeItemComponent, isStandalone: true, selector: "axm-menu-tree-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, canDelete: { classPropertyName: "canDelete", publicName: "canDelete", isSignal: true, isRequired: false, transformFunction: null }, connectedDropLists: { classPropertyName: "connectedDropLists", publicName: "connectedDropLists", isSignal: true, isRequired: false, transformFunction: null }, canMoveUp: { classPropertyName: "canMoveUp", publicName: "canMoveUp", isSignal: true, isRequired: false, transformFunction: null }, canMoveDown: { classPropertyName: "canMoveDown", publicName: "canMoveDown", isSignal: true, isRequired: false, transformFunction: null }, canMoveToParent: { classPropertyName: "canMoveToParent", publicName: "canMoveToParent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionTriggered: "actionTriggered", itemDropped: "itemDropped" }, host: { classAttribute: "axm-menu-tree-item" }, ngImport: i0, template: "<div class=\"axm-menu-tree-item__container\" [style.padding-left.rem]=\"level()\">\n <div\n class=\"axm-menu-tree-item__content\"\n cdkDrag\n [cdkDragData]=\"item()\"\n [class.--hidden]=\"item().isHidden\"\n [class.--custom]=\"item().isCustom\"\n >\n <!-- Drag Handle -->\n <div class=\"axm-menu-tree-item__drag-handle\" cdkDragHandle>\n <i class=\"fa-light fa-grip-dots-vertical\"></i>\n </div>\n\n <!-- Expand/Collapse Button -->\n @if (item().children && item().children!.length > 0) {\n <ax-button class=\"axm-menu-tree-item__expand-btn\" [look]=\"'blank'\" [size]=\"'sm'\" (onClick)=\"toggleExpand()\">\n <i [class]=\"isExpanded() ? 'fa-light fa-chevron-down' : 'fa-light fa-chevron-right'\"></i>\n </ax-button>\n } @else {\n <div class=\"axm-menu-tree-item__spacer\"></div>\n }\n\n <!-- Icon -->\n @if (item().icon) {\n <i class=\"axm-menu-tree-item__icon\" [class]=\"item().icon\"></i>\n }\n\n <!-- Text or Info -->\n @if (item().text) {\n <span class=\"axm-menu-tree-item__text\">\n {{ item().text | translate | async }}\n </span>\n } @else if (item().path) {\n <span class=\"axm-menu-tree-item__path\">\n <i class=\"fa-light fa-link\"></i>\n {{ item().path }}\n </span>\n } @else {\n <span class=\"axm-menu-tree-item__divider\">\n <i class=\"fa-light fa-minus\"></i>\n {{ '@application-management:menu-management.badge.divider' | translate | async }}\n </span>\n }\n\n <!-- Menu Name/Key -->\n @if (item().name) {\n <code class=\"axm-menu-tree-item__name\">\n {{ item().name }}\n </code>\n }\n\n <!-- Actions Dropdown -->\n <ax-button class=\"axm-menu-tree-item__actions\" [look]=\"'blank'\" [size]=\"'sm'\" [iconOnly]=\"true\">\n <ax-prefix>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </ax-prefix>\n\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n <!-- Show/Hide (only for items with names) -->\n @if (item().name && item().isBuiltIn) {\n @if (item().isHidden) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.show' | translate | async)!\"\n (onClick)=\"onAction('show'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-eye\"></i>\n </ax-prefix>\n </ax-button-item>\n } @else {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.hide' | translate | async)!\"\n (onClick)=\"onAction('hide'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-eye-slash\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n }\n\n <!-- Edit (only for items with names) -->\n @if (item().name) {\n <ax-button-item\n [text]=\"('@general:actions.edit.title' | translate | async)!\"\n (onClick)=\"onAction('edit'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-edit\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Add Child -->\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.add-child' | translate | async)!\"\n (onClick)=\"onAction('add-child'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-plus\"></i>\n </ax-prefix>\n </ax-button-item>\n\n <!-- Move Actions -->\n @if (canMoveUp() || canMoveDown() || canMoveToParent()) {\n <ax-divider></ax-divider>\n\n <!-- Move Up -->\n @if (canMoveUp()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-up' | translate | async)!\"\n (onClick)=\"onAction('move-up'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-arrow-up\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Move Down -->\n @if (canMoveDown()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-down' | translate | async)!\"\n (onClick)=\"onAction('move-down'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-arrow-down\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Move to Parent -->\n @if (canMoveToParent()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-to-parent' | translate | async)!\"\n (onClick)=\"onAction('move-to-parent'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-level-up\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n }\n\n <!-- Delete (only for custom items with names) -->\n @if (item().name && item().isCustom && canDelete()) {\n <ax-divider></ax-divider>\n <ax-button-item\n [color]=\"'danger'\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n (onClick)=\"onAction('delete'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-trash\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Info message for items without names -->\n @if (!item().name) {\n <ax-divider></ax-divider>\n <div class=\"ax-p-2 ax-text-xs ax-text-neutral-500 ax-italic\">\n {{ '@application-management:menu-management.messages.no-name-info' | translate | async }}\n </div>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n </div>\n\n <!-- Children Drop Zone - Always render the drop list even when collapsed -->\n <!-- The drop list must exist for items to be draggable FROM this container -->\n <div\n class=\"axm-menu-tree-item__children\"\n cdkDropList\n [id]=\"getDropListId()\"\n [cdkDropListData]=\"getChildrenArray()\"\n [cdkDropListConnectedTo]=\"connectedDropLists()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n [class.--collapsed]=\"!isExpanded()\"\n >\n @if (isExpanded()) {\n @for (child of getChildrenArray(); track child.name || child.path || $index; let i = $index) {\n <axm-menu-tree-item\n [item]=\"child\"\n [level]=\"level() + 1\"\n [canDelete]=\"canDelete()\"\n [connectedDropLists]=\"connectedDropLists()\"\n [canMoveUp]=\"i > 0\"\n [canMoveDown]=\"i < getChildrenArray().length - 1\"\n [canMoveToParent]=\"true\"\n (actionTriggered)=\"actionTriggered.emit($event)\"\n (itemDropped)=\"itemDropped.emit($event)\"\n />\n }\n }\n </div>\n</div>\n", styles: [".axm-menu-tree-item__container{display:flex;flex-direction:column}.axm-menu-tree-item__content{display:flex;align-items:center;gap:.5rem;border-radius:.375rem;padding:.5rem;border-width:1px;background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.axm-menu-tree-item__content:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}.axm-menu-tree-item__content.--hidden{--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));opacity:.5;--tw-border-opacity: 1;border-color:rgb(212 212 212 / var(--tw-border-opacity, 1))}.axm-menu-tree-item__content.--hidden .axm-menu-tree-item__icon{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__content.--hidden .axm-menu-tree-item__name{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__content.--custom{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-tree-item__content.cdk-drag-preview{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-tree-item__drag-handle{display:flex;align-items:center;justify-content:center;height:1.5rem;width:1.5rem;--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1));cursor:move}.axm-menu-tree-item__drag-handle:hover{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__expand-btn{height:1.5rem;width:1.5rem}.axm-menu-tree-item__spacer{width:1.5rem}.axm-menu-tree-item__icon{display:flex;align-items:center;justify-content:center;height:1.5rem;width:1.5rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-primary-500),var(--tw-text-opacity, 1))}.axm-menu-tree-item__text{flex:1 1 0%;font-size:.875rem;line-height:1.25rem;font-weight:500}.axm-menu-tree-item__divider,.axm-menu-tree-item__path{display:flex;flex:1 1 0%;align-items:center;gap:.5rem;font-size:.75rem;line-height:1rem;font-style:italic;--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__divider i,.axm-menu-tree-item__path i{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__name{display:flex;align-items:center;border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;line-height:1rem;--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(64 64 64 / var(--tw-text-opacity, 1));font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-weight:400;border-width:1px;--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1));white-space:nowrap;-webkit-user-select:all;-moz-user-select:all;user-select:all}.axm-menu-tree-item__actions{margin-left:.5rem}.axm-menu-tree-item__children{display:flex;flex-direction:column;gap:.5rem;margin-top:.5rem;padding-left:1rem;border-left-width:2px;--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1))}.axm-menu-tree-item__children.--collapsed{display:none}\n"], dependencies: [{ kind: "component", type: AXMMenuTreeItemComponent, selector: "axm-menu-tree-item", inputs: ["item", "level", "canDelete", "connectedDropLists", "canMoveUp", "canMoveDown", "canMoveToParent"], outputs: ["actionTriggered", "itemDropped"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "component", type: i1.AXButtonItemComponent, selector: "ax-button-item", inputs: ["color", "disabled", "text", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange"] }, { kind: "component", type: i1.AXButtonItemListComponent, selector: "ax-button-item-list", inputs: ["items"], outputs: ["onItemClick"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i2.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXDropdownModule }, { kind: "component", type: i3.AXDropdownPanelComponent, selector: "ax-dropdown-panel", inputs: ["isOpen", "fitParent", "dropdownWidth", "position", "placement", "_target", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
87
|
-
}
|
|
88
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuTreeItemComponent, decorators: [{
|
|
89
|
-
type: Component,
|
|
90
|
-
args: [{ selector: 'axm-menu-tree-item', imports: [
|
|
91
|
-
CommonModule,
|
|
92
|
-
CdkDrag,
|
|
93
|
-
CdkDragHandle,
|
|
94
|
-
CdkDropList,
|
|
95
|
-
AXButtonModule,
|
|
96
|
-
AXDecoratorModule,
|
|
97
|
-
AXDropdownModule,
|
|
98
|
-
AXTranslationModule,
|
|
99
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'axm-menu-tree-item' }, template: "<div class=\"axm-menu-tree-item__container\" [style.padding-left.rem]=\"level()\">\n <div\n class=\"axm-menu-tree-item__content\"\n cdkDrag\n [cdkDragData]=\"item()\"\n [class.--hidden]=\"item().isHidden\"\n [class.--custom]=\"item().isCustom\"\n >\n <!-- Drag Handle -->\n <div class=\"axm-menu-tree-item__drag-handle\" cdkDragHandle>\n <i class=\"fa-light fa-grip-dots-vertical\"></i>\n </div>\n\n <!-- Expand/Collapse Button -->\n @if (item().children && item().children!.length > 0) {\n <ax-button class=\"axm-menu-tree-item__expand-btn\" [look]=\"'blank'\" [size]=\"'sm'\" (onClick)=\"toggleExpand()\">\n <i [class]=\"isExpanded() ? 'fa-light fa-chevron-down' : 'fa-light fa-chevron-right'\"></i>\n </ax-button>\n } @else {\n <div class=\"axm-menu-tree-item__spacer\"></div>\n }\n\n <!-- Icon -->\n @if (item().icon) {\n <i class=\"axm-menu-tree-item__icon\" [class]=\"item().icon\"></i>\n }\n\n <!-- Text or Info -->\n @if (item().text) {\n <span class=\"axm-menu-tree-item__text\">\n {{ item().text | translate | async }}\n </span>\n } @else if (item().path) {\n <span class=\"axm-menu-tree-item__path\">\n <i class=\"fa-light fa-link\"></i>\n {{ item().path }}\n </span>\n } @else {\n <span class=\"axm-menu-tree-item__divider\">\n <i class=\"fa-light fa-minus\"></i>\n {{ '@application-management:menu-management.badge.divider' | translate | async }}\n </span>\n }\n\n <!-- Menu Name/Key -->\n @if (item().name) {\n <code class=\"axm-menu-tree-item__name\">\n {{ item().name }}\n </code>\n }\n\n <!-- Actions Dropdown -->\n <ax-button class=\"axm-menu-tree-item__actions\" [look]=\"'blank'\" [size]=\"'sm'\" [iconOnly]=\"true\">\n <ax-prefix>\n <i class=\"fa-light fa-ellipsis-vertical\"></i>\n </ax-prefix>\n\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n <!-- Show/Hide (only for items with names) -->\n @if (item().name && item().isBuiltIn) {\n @if (item().isHidden) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.show' | translate | async)!\"\n (onClick)=\"onAction('show'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-eye\"></i>\n </ax-prefix>\n </ax-button-item>\n } @else {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.hide' | translate | async)!\"\n (onClick)=\"onAction('hide'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-eye-slash\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n }\n\n <!-- Edit (only for items with names) -->\n @if (item().name) {\n <ax-button-item\n [text]=\"('@general:actions.edit.title' | translate | async)!\"\n (onClick)=\"onAction('edit'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-edit\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Add Child -->\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.add-child' | translate | async)!\"\n (onClick)=\"onAction('add-child'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-plus\"></i>\n </ax-prefix>\n </ax-button-item>\n\n <!-- Move Actions -->\n @if (canMoveUp() || canMoveDown() || canMoveToParent()) {\n <ax-divider></ax-divider>\n\n <!-- Move Up -->\n @if (canMoveUp()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-up' | translate | async)!\"\n (onClick)=\"onAction('move-up'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-arrow-up\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Move Down -->\n @if (canMoveDown()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-down' | translate | async)!\"\n (onClick)=\"onAction('move-down'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-arrow-down\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Move to Parent -->\n @if (canMoveToParent()) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.move-to-parent' | translate | async)!\"\n (onClick)=\"onAction('move-to-parent'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-level-up\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n }\n\n <!-- Delete (only for custom items with names) -->\n @if (item().name && item().isCustom && canDelete()) {\n <ax-divider></ax-divider>\n <ax-button-item\n [color]=\"'danger'\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n (onClick)=\"onAction('delete'); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-trash\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Info message for items without names -->\n @if (!item().name) {\n <ax-divider></ax-divider>\n <div class=\"ax-p-2 ax-text-xs ax-text-neutral-500 ax-italic\">\n {{ '@application-management:menu-management.messages.no-name-info' | translate | async }}\n </div>\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n </div>\n\n <!-- Children Drop Zone - Always render the drop list even when collapsed -->\n <!-- The drop list must exist for items to be draggable FROM this container -->\n <div\n class=\"axm-menu-tree-item__children\"\n cdkDropList\n [id]=\"getDropListId()\"\n [cdkDropListData]=\"getChildrenArray()\"\n [cdkDropListConnectedTo]=\"connectedDropLists()\"\n (cdkDropListDropped)=\"onDrop($event)\"\n [class.--collapsed]=\"!isExpanded()\"\n >\n @if (isExpanded()) {\n @for (child of getChildrenArray(); track child.name || child.path || $index; let i = $index) {\n <axm-menu-tree-item\n [item]=\"child\"\n [level]=\"level() + 1\"\n [canDelete]=\"canDelete()\"\n [connectedDropLists]=\"connectedDropLists()\"\n [canMoveUp]=\"i > 0\"\n [canMoveDown]=\"i < getChildrenArray().length - 1\"\n [canMoveToParent]=\"true\"\n (actionTriggered)=\"actionTriggered.emit($event)\"\n (itemDropped)=\"itemDropped.emit($event)\"\n />\n }\n }\n </div>\n</div>\n", styles: [".axm-menu-tree-item__container{display:flex;flex-direction:column}.axm-menu-tree-item__content{display:flex;align-items:center;gap:.5rem;border-radius:.375rem;padding:.5rem;border-width:1px;background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.axm-menu-tree-item__content:hover{background-color:rgb(var(--ax-sys-color-lighter-surface));color:rgb(var(--ax-sys-color-on-lighter-surface));border-color:rgb(var(--ax-sys-color-border-lighter-surface))}.axm-menu-tree-item__content.--hidden{--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1));opacity:.5;--tw-border-opacity: 1;border-color:rgb(212 212 212 / var(--tw-border-opacity, 1))}.axm-menu-tree-item__content.--hidden .axm-menu-tree-item__icon{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__content.--hidden .axm-menu-tree-item__name{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__content.--custom{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-tree-item__content.cdk-drag-preview{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow);background-color:rgb(var(--ax-sys-color-lightest-surface));color:rgb(var(--ax-sys-color-on-lightest-surface));border-color:rgb(var(--ax-sys-color-border-lightest-surface));border-width:1px;--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-tree-item__drag-handle{display:flex;align-items:center;justify-content:center;height:1.5rem;width:1.5rem;--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1));cursor:move}.axm-menu-tree-item__drag-handle:hover{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__expand-btn{height:1.5rem;width:1.5rem}.axm-menu-tree-item__spacer{width:1.5rem}.axm-menu-tree-item__icon{display:flex;align-items:center;justify-content:center;height:1.5rem;width:1.5rem;--tw-text-opacity: 1;color:rgba(var(--ax-sys-color-primary-500),var(--tw-text-opacity, 1))}.axm-menu-tree-item__text{flex:1 1 0%;font-size:.875rem;line-height:1.25rem;font-weight:500}.axm-menu-tree-item__divider,.axm-menu-tree-item__path{display:flex;flex:1 1 0%;align-items:center;gap:.5rem;font-size:.75rem;line-height:1rem;font-style:italic;--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__divider i,.axm-menu-tree-item__path i{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-tree-item__name{display:flex;align-items:center;border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;line-height:1rem;--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(64 64 64 / var(--tw-text-opacity, 1));font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-weight:400;border-width:1px;--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1));white-space:nowrap;-webkit-user-select:all;-moz-user-select:all;user-select:all}.axm-menu-tree-item__actions{margin-left:.5rem}.axm-menu-tree-item__children{display:flex;flex-direction:column;gap:.5rem;margin-top:.5rem;padding-left:1rem;border-left-width:2px;--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1))}.axm-menu-tree-item__children.--collapsed{display:none}\n"] }]
|
|
100
|
-
}] });
|
|
101
|
-
|
|
102
|
-
//#region ---- Menu Management Service ----
|
|
103
|
-
class AXPMenuManagementService {
|
|
104
|
-
constructor() {
|
|
105
|
-
//#region ---- Dependencies ----
|
|
106
|
-
this.settingService = inject(AXPSettingService);
|
|
107
|
-
this.menuProviderService = inject(AXPMenuProviderService);
|
|
108
|
-
this.menuService = inject(AXPMenuService);
|
|
109
|
-
}
|
|
110
|
-
//#endregion
|
|
111
|
-
//#region ---- Public Methods ----
|
|
112
|
-
/**
|
|
113
|
-
* Get all menu items with customization metadata
|
|
114
|
-
*/
|
|
115
|
-
async getMenuTree(scope) {
|
|
116
|
-
// Get RAW menu items (all items including hidden ones) - NO middleware to avoid duplicates
|
|
117
|
-
const rawItems = await this.menuProviderService.rawItems();
|
|
118
|
-
// Load customizations for the given scope
|
|
119
|
-
const customization = await this.loadCustomization(scope);
|
|
120
|
-
// Convert raw items to nodes
|
|
121
|
-
const nodes = this.convertToNodes(rawItems, customization);
|
|
122
|
-
// Add custom items to the management view
|
|
123
|
-
const customNodes = this.convertCustomItemsToNodes(customization.customItems, customization);
|
|
124
|
-
nodes.push(...customNodes);
|
|
125
|
-
// Apply parent overrides to restructure the tree (this will move Sample 1 under Profile)
|
|
126
|
-
const finalNodes = this.applyParentOverrides(nodes, customization);
|
|
127
|
-
// Apply the EXACT same sorting logic as the sidebar for perfect synchronization
|
|
128
|
-
const sortedNodes = this.applySidebarSorting(finalNodes);
|
|
129
|
-
return sortedNodes;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Load menu customization for a specific scope
|
|
133
|
-
*/
|
|
134
|
-
async loadCustomization(scope) {
|
|
135
|
-
try {
|
|
136
|
-
const scopedSettings = this.settingService.scope(scope);
|
|
137
|
-
const saved = await scopedSettings.get(AXP_MENU_CUSTOMIZATION_KEY);
|
|
138
|
-
if (saved && saved.version) {
|
|
139
|
-
return saved;
|
|
140
|
-
}
|
|
141
|
-
return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Save menu customization for a specific scope
|
|
149
|
-
*/
|
|
150
|
-
async saveCustomization(scope, customization) {
|
|
151
|
-
const scopedSettings = this.settingService.scope(scope);
|
|
152
|
-
await scopedSettings.set(AXP_MENU_CUSTOMIZATION_KEY, customization);
|
|
153
|
-
// Clear menu cache to force reload
|
|
154
|
-
this.menuProviderService.clearCache();
|
|
155
|
-
// Refresh the menu store to update the sidebar immediately
|
|
156
|
-
const freshItems = await this.menuProviderService.items();
|
|
157
|
-
this.menuService.setMenuItems(freshItems);
|
|
158
|
-
// Force a small delay to ensure the signal update propagates
|
|
159
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Hide a menu item
|
|
163
|
-
*/
|
|
164
|
-
async hideMenuItem(scope, menuName) {
|
|
165
|
-
if (!menuName) {
|
|
166
|
-
throw new Error('Menu item must have a name to be hidden');
|
|
167
|
-
}
|
|
168
|
-
const customization = await this.loadCustomization(scope);
|
|
169
|
-
if (!customization.overrides[menuName]) {
|
|
170
|
-
customization.overrides[menuName] = {};
|
|
171
|
-
}
|
|
172
|
-
customization.overrides[menuName].hidden = true;
|
|
173
|
-
await this.saveCustomization(scope, customization);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Show a menu item
|
|
177
|
-
*/
|
|
178
|
-
async showMenuItem(scope, menuName) {
|
|
179
|
-
if (!menuName) {
|
|
180
|
-
throw new Error('Menu item must have a name to be shown');
|
|
181
|
-
}
|
|
182
|
-
const customization = await this.loadCustomization(scope);
|
|
183
|
-
if (customization.overrides[menuName]) {
|
|
184
|
-
delete customization.overrides[menuName].hidden;
|
|
185
|
-
// Clean up if no other overrides exist
|
|
186
|
-
if (Object.keys(customization.overrides[menuName]).length === 0) {
|
|
187
|
-
delete customization.overrides[menuName];
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
await this.saveCustomization(scope, customization);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Update menu item priority
|
|
194
|
-
*/
|
|
195
|
-
async updateMenuPriority(scope, menuName, priority) {
|
|
196
|
-
const customization = await this.loadCustomization(scope);
|
|
197
|
-
if (!customization.overrides[menuName]) {
|
|
198
|
-
customization.overrides[menuName] = {};
|
|
199
|
-
}
|
|
200
|
-
customization.overrides[menuName].priority = priority;
|
|
201
|
-
await this.saveCustomization(scope, customization);
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Move menu item to different parent
|
|
205
|
-
*/
|
|
206
|
-
async moveMenuItem(scope, menuName, newParentName) {
|
|
207
|
-
const customization = await this.loadCustomization(scope);
|
|
208
|
-
if (!customization.overrides[menuName]) {
|
|
209
|
-
customization.overrides[menuName] = {};
|
|
210
|
-
}
|
|
211
|
-
if (newParentName) {
|
|
212
|
-
customization.overrides[menuName].parentName = newParentName;
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
delete customization.overrides[menuName].parentName;
|
|
216
|
-
}
|
|
217
|
-
await this.saveCustomization(scope, customization);
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Update menu item properties
|
|
221
|
-
*/
|
|
222
|
-
async updateMenuProperties(scope, menuName, properties) {
|
|
223
|
-
const customization = await this.loadCustomization(scope);
|
|
224
|
-
if (!customization.overrides[menuName]) {
|
|
225
|
-
customization.overrides[menuName] = {};
|
|
226
|
-
}
|
|
227
|
-
customization.overrides[menuName].properties = {
|
|
228
|
-
...customization.overrides[menuName].properties,
|
|
229
|
-
...properties,
|
|
230
|
-
};
|
|
231
|
-
await this.saveCustomization(scope, customization);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Add custom menu item
|
|
235
|
-
*/
|
|
236
|
-
async addCustomMenuItem(scope, menuItem) {
|
|
237
|
-
const customization = await this.loadCustomization(scope);
|
|
238
|
-
// Generate unique name if not provided
|
|
239
|
-
if (!menuItem.name) {
|
|
240
|
-
menuItem.name = `custom-menu-${Date.now()}`;
|
|
241
|
-
}
|
|
242
|
-
customization.customItems.push(menuItem);
|
|
243
|
-
await this.saveCustomization(scope, customization);
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Add custom menu item as child of existing item
|
|
247
|
-
*/
|
|
248
|
-
async addCustomMenuItemAsChild(scope, menuItem, parentName) {
|
|
249
|
-
const customization = await this.loadCustomization(scope);
|
|
250
|
-
// Generate unique name if not provided
|
|
251
|
-
if (!menuItem.name) {
|
|
252
|
-
menuItem.name = `custom-menu-${Date.now()}`;
|
|
253
|
-
}
|
|
254
|
-
// Add custom item
|
|
255
|
-
customization.customItems.push(menuItem);
|
|
256
|
-
// Set parent override to move it under the specified parent
|
|
257
|
-
if (!customization.overrides[menuItem.name]) {
|
|
258
|
-
customization.overrides[menuItem.name] = {};
|
|
259
|
-
}
|
|
260
|
-
customization.overrides[menuItem.name].parentName = parentName;
|
|
261
|
-
await this.saveCustomization(scope, customization);
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Update custom menu item
|
|
265
|
-
*/
|
|
266
|
-
async updateCustomMenuItem(scope, menuName, menuItem) {
|
|
267
|
-
const customization = await this.loadCustomization(scope);
|
|
268
|
-
const index = customization.customItems.findIndex((item) => item.name === menuName);
|
|
269
|
-
if (index !== -1) {
|
|
270
|
-
customization.customItems[index] = menuItem;
|
|
271
|
-
await this.saveCustomization(scope, customization);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Delete custom menu item
|
|
276
|
-
*/
|
|
277
|
-
async deleteCustomMenuItem(scope, menuName) {
|
|
278
|
-
const customization = await this.loadCustomization(scope);
|
|
279
|
-
customization.customItems = customization.customItems.filter((item) => item.name !== menuName);
|
|
280
|
-
await this.saveCustomization(scope, customization);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Reset all customizations for a scope
|
|
284
|
-
*/
|
|
285
|
-
async resetCustomizations(scope) {
|
|
286
|
-
await this.saveCustomization(scope, cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT));
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Reorder menu items based on drag-drop
|
|
290
|
-
*/
|
|
291
|
-
async reorderMenuItems(scope, items, parentName) {
|
|
292
|
-
const customization = await this.loadCustomization(scope);
|
|
293
|
-
// Update priorities for all items at this level (only for items with names)
|
|
294
|
-
items.forEach((item, index) => {
|
|
295
|
-
if (item.name) {
|
|
296
|
-
if (!customization.overrides[item.name]) {
|
|
297
|
-
customization.overrides[item.name] = {};
|
|
298
|
-
}
|
|
299
|
-
// Set priority based on index
|
|
300
|
-
customization.overrides[item.name].priority = index * 100;
|
|
301
|
-
// Update parentName to reflect the new parent
|
|
302
|
-
// Use empty string for root level, actual name for nested items
|
|
303
|
-
customization.overrides[item.name].parentName = parentName || '';
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
await this.saveCustomization(scope, customization);
|
|
307
|
-
}
|
|
308
|
-
//#endregion
|
|
309
|
-
//#region ---- Private Methods ----
|
|
310
|
-
/**
|
|
311
|
-
* Apply the EXACT same sorting logic as the sidebar for perfect synchronization
|
|
312
|
-
* Uses the same sortBy logic: [(c) => c.priority ?? 0, (c) => c.text]
|
|
313
|
-
*/
|
|
314
|
-
applySidebarSorting(nodes) {
|
|
315
|
-
return sortBy(nodes, [(c) => c.priority ?? 0, (c) => c.text]).map(node => ({
|
|
316
|
-
...node,
|
|
317
|
-
children: node.children ? this.applySidebarSorting(node.children) : undefined
|
|
318
|
-
}));
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Convert custom items to nodes with metadata
|
|
322
|
-
*/
|
|
323
|
-
convertCustomItemsToNodes(customItems, customization) {
|
|
324
|
-
return customItems.map((item) => {
|
|
325
|
-
const override = item.name ? customization.overrides[item.name] : undefined;
|
|
326
|
-
const node = {
|
|
327
|
-
...item,
|
|
328
|
-
name: item.name,
|
|
329
|
-
isBuiltIn: false,
|
|
330
|
-
isCustom: true,
|
|
331
|
-
isHidden: override?.hidden || false,
|
|
332
|
-
originalPriority: item.priority,
|
|
333
|
-
originalParentName: undefined,
|
|
334
|
-
// Apply priority override if it exists
|
|
335
|
-
priority: override?.priority !== undefined ? override.priority : item.priority,
|
|
336
|
-
// Apply icon override if it exists
|
|
337
|
-
icon: override?.properties?.icon !== undefined ? override.properties.icon : item.icon,
|
|
338
|
-
// Apply text override if it exists
|
|
339
|
-
text: override?.properties?.text !== undefined ? override.properties.text : item.text,
|
|
340
|
-
// Apply path override if it exists
|
|
341
|
-
path: override?.properties?.path !== undefined ? override.properties.path : item.path,
|
|
342
|
-
children: item.children ? this.convertToNodes(item.children, customization) : undefined,
|
|
343
|
-
};
|
|
344
|
-
return node;
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Apply parentName overrides to restructure the tree
|
|
349
|
-
*/
|
|
350
|
-
applyParentOverrides(nodes, customization) {
|
|
351
|
-
// Check if there are any parentName overrides (even if value is undefined)
|
|
352
|
-
const hasParentOverrides = Object.values(customization.overrides).some((override) => 'parentName' in override);
|
|
353
|
-
if (!hasParentOverrides) {
|
|
354
|
-
return nodes;
|
|
355
|
-
}
|
|
356
|
-
// Clone the tree to avoid mutations
|
|
357
|
-
const clonedNodes = this.deepCloneNodes(nodes);
|
|
358
|
-
// Find items that need to be moved based on parentName overrides
|
|
359
|
-
Object.entries(customization.overrides).forEach(([itemName, override]) => {
|
|
360
|
-
// Check if the override has a parentName property (even if it's an empty string)
|
|
361
|
-
if ('parentName' in override) {
|
|
362
|
-
const isRootLevel = !override.parentName || override.parentName === '';
|
|
363
|
-
// Find and remove the item from its current location
|
|
364
|
-
const removedItem = this.removeAndReturnNode(clonedNodes, itemName);
|
|
365
|
-
if (removedItem) {
|
|
366
|
-
if (isRootLevel) {
|
|
367
|
-
// Move to root level
|
|
368
|
-
clonedNodes.push(removedItem);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
// Move to specified parent
|
|
372
|
-
const newParent = this.findNodeByName(clonedNodes, override.parentName);
|
|
373
|
-
if (newParent) {
|
|
374
|
-
if (!newParent.children) {
|
|
375
|
-
newParent.children = [];
|
|
376
|
-
}
|
|
377
|
-
newParent.children.push(removedItem);
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
// Fallback: add to root if parent not found
|
|
381
|
-
clonedNodes.push(removedItem);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
// Re-sort after restructuring
|
|
388
|
-
this.sortNodesRecursive(clonedNodes);
|
|
389
|
-
return clonedNodes;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Remove a node from tree and return it
|
|
393
|
-
*/
|
|
394
|
-
removeAndReturnNode(nodes, name) {
|
|
395
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
396
|
-
if (nodes[i].name === name) {
|
|
397
|
-
const [removed] = nodes.splice(i, 1);
|
|
398
|
-
return removed;
|
|
399
|
-
}
|
|
400
|
-
if (nodes[i].children) {
|
|
401
|
-
const found = this.removeAndReturnNode(nodes[i].children, name);
|
|
402
|
-
if (found) {
|
|
403
|
-
return found;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Deep clone nodes array
|
|
411
|
-
*/
|
|
412
|
-
deepCloneNodes(nodes) {
|
|
413
|
-
return nodes.map((node) => ({
|
|
414
|
-
...node,
|
|
415
|
-
children: node.children ? this.deepCloneNodes(node.children) : undefined,
|
|
416
|
-
}));
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Find a node by name in the tree
|
|
420
|
-
*/
|
|
421
|
-
findNodeByName(nodes, name) {
|
|
422
|
-
for (const node of nodes) {
|
|
423
|
-
if (node.name === name) {
|
|
424
|
-
return node;
|
|
425
|
-
}
|
|
426
|
-
if (node.children) {
|
|
427
|
-
const found = this.findNodeByName(node.children, name);
|
|
428
|
-
if (found) {
|
|
429
|
-
return found;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return null;
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Sort nodes recursively by priority first, then by text (same as sidebar)
|
|
437
|
-
*/
|
|
438
|
-
sortNodesRecursive(nodes) {
|
|
439
|
-
// Sort by priority first, then by text (same as sidebar)
|
|
440
|
-
const sorted = sortBy(nodes, [(c) => c.priority ?? 0, (c) => c.text]);
|
|
441
|
-
nodes.length = 0;
|
|
442
|
-
nodes.push(...sorted);
|
|
443
|
-
nodes.forEach((node) => {
|
|
444
|
-
if (node.children) {
|
|
445
|
-
this.sortNodesRecursive(node.children);
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Convert menu items to nodes with metadata
|
|
451
|
-
*/
|
|
452
|
-
convertToNodes(items, customization) {
|
|
453
|
-
const nodes = items.map((item) => {
|
|
454
|
-
// Only use items that have a proper name property
|
|
455
|
-
const itemName = item.name;
|
|
456
|
-
const override = itemName ? customization.overrides[itemName] : undefined;
|
|
457
|
-
const isCustom = itemName ? customization.customItems.some((ci) => ci.name === itemName) : false;
|
|
458
|
-
const node = {
|
|
459
|
-
...item,
|
|
460
|
-
name: itemName,
|
|
461
|
-
isBuiltIn: !isCustom,
|
|
462
|
-
isCustom,
|
|
463
|
-
isHidden: override?.hidden || false,
|
|
464
|
-
originalPriority: item.priority,
|
|
465
|
-
originalParentName: undefined,
|
|
466
|
-
// Apply priority override if it exists
|
|
467
|
-
priority: override?.priority !== undefined ? override.priority : item.priority,
|
|
468
|
-
// Apply icon override if it exists
|
|
469
|
-
icon: override?.properties?.icon !== undefined ? override.properties.icon : item.icon,
|
|
470
|
-
// Apply text override if it exists
|
|
471
|
-
text: override?.properties?.text !== undefined ? override.properties.text : item.text,
|
|
472
|
-
// Apply path override if it exists
|
|
473
|
-
path: override?.properties?.path !== undefined ? override.properties.path : item.path,
|
|
474
|
-
children: item.children ? this.convertToNodes(item.children, customization) : undefined,
|
|
475
|
-
};
|
|
476
|
-
return node;
|
|
477
|
-
});
|
|
478
|
-
// Don't sort here - sorting is applied at the top level in getMenuTree
|
|
479
|
-
return nodes;
|
|
480
|
-
}
|
|
481
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
482
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, providedIn: 'root' }); }
|
|
483
|
-
}
|
|
484
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, decorators: [{
|
|
485
|
-
type: Injectable,
|
|
486
|
-
args: [{ providedIn: 'root' }]
|
|
487
|
-
}] });
|
|
488
|
-
|
|
489
|
-
//#region ---- Menu List Component ----
|
|
490
|
-
class AXMMenuListComponent extends AXPPageLayoutBaseComponent {
|
|
491
|
-
constructor() {
|
|
492
|
-
super(...arguments);
|
|
493
|
-
//#region ---- Dependencies ----
|
|
494
|
-
this.menuManagementService = inject(AXPMenuManagementService);
|
|
495
|
-
this.layoutBuilder = inject(AXPLayoutBuilderService);
|
|
496
|
-
this.dialogService = inject(AXDialogService);
|
|
497
|
-
this.toastService = inject(AXToastService);
|
|
498
|
-
this.translationService = inject(AXTranslationService);
|
|
499
|
-
this.route = inject(ActivatedRoute);
|
|
500
|
-
//#endregion
|
|
501
|
-
//#region ---- Component State ----
|
|
502
|
-
this.menuItems = signal([], ...(ngDevMode ? [{ debugName: "menuItems" }] : []));
|
|
503
|
-
this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
504
|
-
this.error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
505
|
-
this.currentScope = signal(AXPPlatformScope.User, ...(ngDevMode ? [{ debugName: "currentScope" }] : []));
|
|
506
|
-
this.allDropListIds = signal([], ...(ngDevMode ? [{ debugName: "allDropListIds" }] : []));
|
|
507
|
-
}
|
|
508
|
-
//#endregion
|
|
509
|
-
//#region ---- Lifecycle ----
|
|
510
|
-
async ngOnInit() {
|
|
511
|
-
await super.ngOnInit();
|
|
512
|
-
// Get scope from route
|
|
513
|
-
this.route.params.subscribe(async (params) => {
|
|
514
|
-
const scope = params['scope'];
|
|
515
|
-
if (scope) {
|
|
516
|
-
this.currentScope.set(scope);
|
|
517
|
-
await this.loadMenuItems();
|
|
518
|
-
this.recompute(); // Update page header after loading
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
//#endregion
|
|
523
|
-
//#region ---- Data Loading ----
|
|
524
|
-
/**
|
|
525
|
-
* Load menu items for current scope
|
|
526
|
-
*/
|
|
527
|
-
async loadMenuItems() {
|
|
528
|
-
try {
|
|
529
|
-
this.isLoading.set(true);
|
|
530
|
-
this.error.set('');
|
|
531
|
-
const items = await this.menuManagementService.getMenuTree(this.currentScope());
|
|
532
|
-
this.menuItems.set(items);
|
|
533
|
-
// Collect all drop list IDs for connecting them
|
|
534
|
-
const dropListIds = this.collectDropListIds(items);
|
|
535
|
-
this.allDropListIds.set(dropListIds);
|
|
536
|
-
this.recompute();
|
|
537
|
-
}
|
|
538
|
-
catch (error) {
|
|
539
|
-
this.error.set(error instanceof Error ? error.message : 'Failed to load menu items');
|
|
540
|
-
this.toastService.danger('@application-management:menu-management.messages.load-error');
|
|
541
|
-
}
|
|
542
|
-
finally {
|
|
543
|
-
this.isLoading.set(false);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Recursively collect all drop list IDs from menu tree
|
|
548
|
-
* Includes drop zones for all items, even those without children
|
|
549
|
-
*/
|
|
550
|
-
collectDropListIds(items, ids = []) {
|
|
551
|
-
// Add root only once at the start
|
|
552
|
-
if (ids.length === 0) {
|
|
553
|
-
ids.push('menu-root');
|
|
554
|
-
}
|
|
555
|
-
items.forEach((item) => {
|
|
556
|
-
// Add drop list ID for every item (they all can have children dropped into them)
|
|
557
|
-
const dropListId = `menu-${item.name || item.path || 'unnamed'}`;
|
|
558
|
-
if (!ids.includes(dropListId)) {
|
|
559
|
-
ids.push(dropListId);
|
|
560
|
-
}
|
|
561
|
-
// Recursively collect from children if they exist
|
|
562
|
-
if (item.children && item.children.length > 0) {
|
|
563
|
-
this.collectDropListIds(item.children, ids);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
return ids;
|
|
567
|
-
}
|
|
568
|
-
//#endregion
|
|
569
|
-
//#region ---- Page Layout Interface ----
|
|
570
|
-
/**
|
|
571
|
-
* Get page title
|
|
572
|
-
*/
|
|
573
|
-
async getPageTitle() {
|
|
574
|
-
const scopeName = this.currentScope();
|
|
575
|
-
const scopeKey = scopeName === AXPPlatformScope.User ? 'user' : 'tenant';
|
|
576
|
-
return this.translationService.translateAsync(`@application-management:menu-management.${scopeKey}.title`);
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Get page description
|
|
580
|
-
*/
|
|
581
|
-
async getPageDescription() {
|
|
582
|
-
return this.translationService.translateAsync('@application-management:menu-management.description');
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Get primary menu items (actions)
|
|
586
|
-
*/
|
|
587
|
-
async getPrimaryMenuItems() {
|
|
588
|
-
return [
|
|
589
|
-
{
|
|
590
|
-
title: await this.translationService.translateAsync('@application-management:menu-management.actions.add-root'),
|
|
591
|
-
icon: 'fa-light fa-plus',
|
|
592
|
-
color: 'primary',
|
|
593
|
-
command: { name: 'add-root' },
|
|
594
|
-
},
|
|
595
|
-
];
|
|
596
|
-
}
|
|
597
|
-
/**
|
|
598
|
-
* Get secondary menu items
|
|
599
|
-
*/
|
|
600
|
-
async getSecondaryMenuItems() {
|
|
601
|
-
return [
|
|
602
|
-
{
|
|
603
|
-
title: await this.translationService.translateAsync('@application-management:menu-management.actions.reset'),
|
|
604
|
-
icon: 'fa-light fa-rotate-left',
|
|
605
|
-
color: 'danger',
|
|
606
|
-
command: { name: 'reset' },
|
|
607
|
-
},
|
|
608
|
-
];
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Execute commands from page actions
|
|
612
|
-
*/
|
|
613
|
-
async execute(command) {
|
|
614
|
-
switch (command.name) {
|
|
615
|
-
case 'add-root':
|
|
616
|
-
await this.addRootMenuItem();
|
|
617
|
-
break;
|
|
618
|
-
case 'reset':
|
|
619
|
-
await this.resetCustomizations();
|
|
620
|
-
break;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region ---- Event Handlers ----
|
|
625
|
-
/**
|
|
626
|
-
* Handle menu item action
|
|
627
|
-
*/
|
|
628
|
-
async onActionTriggered(event) {
|
|
629
|
-
const { action, item } = event;
|
|
630
|
-
switch (action) {
|
|
631
|
-
case 'show':
|
|
632
|
-
await this.showMenuItem(item);
|
|
633
|
-
break;
|
|
634
|
-
case 'hide':
|
|
635
|
-
await this.hideMenuItem(item);
|
|
636
|
-
break;
|
|
637
|
-
case 'edit':
|
|
638
|
-
await this.editMenuItem(item);
|
|
639
|
-
break;
|
|
640
|
-
case 'delete':
|
|
641
|
-
await this.deleteMenuItem(item);
|
|
642
|
-
break;
|
|
643
|
-
case 'add-child':
|
|
644
|
-
await this.addChildMenuItem(item);
|
|
645
|
-
break;
|
|
646
|
-
case 'move-up':
|
|
647
|
-
await this.moveItemUp(item);
|
|
648
|
-
break;
|
|
649
|
-
case 'move-down':
|
|
650
|
-
await this.moveItemDown(item);
|
|
651
|
-
break;
|
|
652
|
-
case 'move-to-parent':
|
|
653
|
-
await this.moveItemToParent(item);
|
|
654
|
-
break;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Handle drag and drop
|
|
659
|
-
*/
|
|
660
|
-
async onItemDropped(event) {
|
|
661
|
-
try {
|
|
662
|
-
// IMPORTANT: Find parent containers BEFORE modifying arrays
|
|
663
|
-
const targetParent = this.findParentOfContainer(event.container.data, this.menuItems());
|
|
664
|
-
const targetParentName = targetParent?.name ?? null;
|
|
665
|
-
const sourceParent = this.findParentOfContainer(event.previousContainer.data, this.menuItems());
|
|
666
|
-
const sourceParentName = sourceParent?.name ?? null;
|
|
667
|
-
// Now perform the array operations
|
|
668
|
-
if (event.previousContainer === event.container) {
|
|
669
|
-
// Same container - just reorder
|
|
670
|
-
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
|
671
|
-
}
|
|
672
|
-
else {
|
|
673
|
-
// Different container - move between parents
|
|
674
|
-
transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
|
|
675
|
-
}
|
|
676
|
-
// Update signal to reflect the new order immediately
|
|
677
|
-
this.menuItems.set([...this.menuItems()]);
|
|
678
|
-
// Save the target container with correct parent context
|
|
679
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), event.container.data, targetParentName);
|
|
680
|
-
// If moved between containers, also update the source container
|
|
681
|
-
if (event.previousContainer !== event.container) {
|
|
682
|
-
// Only reorder source if it still has items
|
|
683
|
-
if (event.previousContainer.data.length > 0) {
|
|
684
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), event.previousContainer.data, sourceParentName);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
this.toastService.success('@application-management:menu-management.messages.reorder-success');
|
|
688
|
-
// Force change detection by updating the signal
|
|
689
|
-
// This ensures the UI reflects the changes without reloading from service
|
|
690
|
-
this.menuItems.set([...this.menuItems()]);
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
this.toastService.danger('@application-management:menu-management.messages.reorder-error');
|
|
694
|
-
// Reload on error to revert changes
|
|
695
|
-
await this.loadMenuItems();
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Find the parent item that owns a given children array
|
|
700
|
-
* Returns the parent node, or null if the container is at root level
|
|
701
|
-
*/
|
|
702
|
-
findParentOfContainer(containerData, items) {
|
|
703
|
-
// Check if this is the root level
|
|
704
|
-
if (containerData === items) {
|
|
705
|
-
return null; // Root level has no parent
|
|
706
|
-
}
|
|
707
|
-
// Recursively search for the parent
|
|
708
|
-
for (const item of items) {
|
|
709
|
-
// Check if this item's children array is the target container
|
|
710
|
-
if (item.children === containerData) {
|
|
711
|
-
return item;
|
|
712
|
-
}
|
|
713
|
-
// Recursively search in children
|
|
714
|
-
if (item.children && item.children.length > 0) {
|
|
715
|
-
const found = this.findParentOfContainer(containerData, item.children);
|
|
716
|
-
if (found !== null) {
|
|
717
|
-
return found;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
// Not found - this shouldn't happen in normal operation
|
|
722
|
-
return null;
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Find an item in the tree and return its parent, container, and index
|
|
726
|
-
*/
|
|
727
|
-
findItemAndParent(targetItem, items, parent = null) {
|
|
728
|
-
// Search in current level
|
|
729
|
-
const index = items.findIndex(item => item === targetItem);
|
|
730
|
-
if (index !== -1) {
|
|
731
|
-
return { parent, container: items, index };
|
|
732
|
-
}
|
|
733
|
-
// Search in children
|
|
734
|
-
for (const item of items) {
|
|
735
|
-
if (item.children && item.children.length > 0) {
|
|
736
|
-
const found = this.findItemAndParent(targetItem, item.children, item);
|
|
737
|
-
if (found) {
|
|
738
|
-
return found;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
return null;
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* Add new root menu item
|
|
746
|
-
*/
|
|
747
|
-
async addRootMenuItem() {
|
|
748
|
-
await this.showMenuItemDialog(null);
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Reset customizations
|
|
752
|
-
*/
|
|
753
|
-
async resetCustomizations() {
|
|
754
|
-
const confirmed = await this.dialogService.confirm('@application-management:menu-management.reset.title', '@application-management:menu-management.reset.message');
|
|
755
|
-
if (!confirmed)
|
|
756
|
-
return;
|
|
757
|
-
try {
|
|
758
|
-
await this.menuManagementService.resetCustomizations(this.currentScope());
|
|
759
|
-
this.toastService.success('@application-management:menu-management.messages.reset-success');
|
|
760
|
-
await this.loadMenuItems();
|
|
761
|
-
}
|
|
762
|
-
catch (error) {
|
|
763
|
-
this.toastService.danger('@application-management:menu-management.messages.reset-error');
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
//#endregion
|
|
767
|
-
//#region ---- Menu Item Actions ----
|
|
768
|
-
/**
|
|
769
|
-
* Show menu item
|
|
770
|
-
*/
|
|
771
|
-
async showMenuItem(item) {
|
|
772
|
-
try {
|
|
773
|
-
if (!item.name)
|
|
774
|
-
return;
|
|
775
|
-
await this.menuManagementService.showMenuItem(this.currentScope(), item.name);
|
|
776
|
-
this.toastService.success('@application-management:menu-management.messages.show-success');
|
|
777
|
-
await this.loadMenuItems();
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
this.toastService.danger('@application-management:menu-management.messages.show-error');
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Hide menu item
|
|
785
|
-
*/
|
|
786
|
-
async hideMenuItem(item) {
|
|
787
|
-
try {
|
|
788
|
-
if (!item.name)
|
|
789
|
-
return;
|
|
790
|
-
await this.menuManagementService.hideMenuItem(this.currentScope(), item.name);
|
|
791
|
-
this.toastService.success('@application-management:menu-management.messages.hide-success');
|
|
792
|
-
await this.loadMenuItems();
|
|
793
|
-
}
|
|
794
|
-
catch (error) {
|
|
795
|
-
this.toastService.danger('@application-management:menu-management.messages.hide-error');
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Edit menu item
|
|
800
|
-
*/
|
|
801
|
-
async editMenuItem(item) {
|
|
802
|
-
await this.showMenuItemDialog(item);
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Delete custom menu item
|
|
806
|
-
*/
|
|
807
|
-
async deleteMenuItem(item) {
|
|
808
|
-
const confirmed = await this.dialogService.confirm('@general:actions.delete.title', '@application-management:menu-management.delete.message');
|
|
809
|
-
if (!confirmed)
|
|
810
|
-
return;
|
|
811
|
-
try {
|
|
812
|
-
if (!item.name)
|
|
813
|
-
return;
|
|
814
|
-
await this.menuManagementService.deleteCustomMenuItem(this.currentScope(), item.name);
|
|
815
|
-
this.toastService.success('@application-management:menu-management.messages.delete-success');
|
|
816
|
-
await this.loadMenuItems();
|
|
817
|
-
}
|
|
818
|
-
catch (error) {
|
|
819
|
-
this.toastService.danger('@application-management:menu-management.messages.delete-error');
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
* Add child menu item
|
|
824
|
-
*/
|
|
825
|
-
async addChildMenuItem(parent) {
|
|
826
|
-
await this.showMenuItemDialog(null, parent);
|
|
827
|
-
}
|
|
828
|
-
/**
|
|
829
|
-
* Move item up in its parent container
|
|
830
|
-
*/
|
|
831
|
-
async moveItemUp(item) {
|
|
832
|
-
try {
|
|
833
|
-
const result = this.findItemAndParent(item, this.menuItems());
|
|
834
|
-
if (!result)
|
|
835
|
-
return;
|
|
836
|
-
const { parent, container, index } = result;
|
|
837
|
-
if (index <= 0)
|
|
838
|
-
return; // Already at top
|
|
839
|
-
// Swap with previous item
|
|
840
|
-
moveItemInArray(container, index, index - 1);
|
|
841
|
-
// Update signal to reflect the change
|
|
842
|
-
this.menuItems.set([...this.menuItems()]);
|
|
843
|
-
// Save the reordered container
|
|
844
|
-
const parentName = parent?.name ?? null;
|
|
845
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), container, parentName);
|
|
846
|
-
this.toastService.success('@application-management:menu-management.messages.reorder-success');
|
|
847
|
-
}
|
|
848
|
-
catch (error) {
|
|
849
|
-
this.toastService.danger('@application-management:menu-management.messages.reorder-error');
|
|
850
|
-
await this.loadMenuItems();
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Move item down in its parent container
|
|
855
|
-
*/
|
|
856
|
-
async moveItemDown(item) {
|
|
857
|
-
try {
|
|
858
|
-
const result = this.findItemAndParent(item, this.menuItems());
|
|
859
|
-
if (!result)
|
|
860
|
-
return;
|
|
861
|
-
const { parent, container, index } = result;
|
|
862
|
-
if (index >= container.length - 1)
|
|
863
|
-
return; // Already at bottom
|
|
864
|
-
// Swap with next item
|
|
865
|
-
moveItemInArray(container, index, index + 1);
|
|
866
|
-
// Update signal to reflect the change
|
|
867
|
-
this.menuItems.set([...this.menuItems()]);
|
|
868
|
-
// Save the reordered container
|
|
869
|
-
const parentName = parent?.name ?? null;
|
|
870
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), container, parentName);
|
|
871
|
-
this.toastService.success('@application-management:menu-management.messages.reorder-success');
|
|
872
|
-
}
|
|
873
|
-
catch (error) {
|
|
874
|
-
this.toastService.danger('@application-management:menu-management.messages.reorder-error');
|
|
875
|
-
await this.loadMenuItems();
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Move item to its parent's level (move out of nested level)
|
|
880
|
-
*/
|
|
881
|
-
async moveItemToParent(item) {
|
|
882
|
-
try {
|
|
883
|
-
const result = this.findItemAndParent(item, this.menuItems());
|
|
884
|
-
if (!result || !result.parent)
|
|
885
|
-
return; // No parent to move to
|
|
886
|
-
const { parent, container, index } = result;
|
|
887
|
-
// Find the grandparent container
|
|
888
|
-
const grandParentResult = this.findItemAndParent(parent, this.menuItems());
|
|
889
|
-
const targetContainer = grandParentResult ? grandParentResult.container : this.menuItems();
|
|
890
|
-
const parentIndexInGrandparent = grandParentResult ? grandParentResult.index : this.menuItems().indexOf(parent);
|
|
891
|
-
// Remove from current container
|
|
892
|
-
const [movedItem] = container.splice(index, 1);
|
|
893
|
-
// Insert after parent in grandparent container
|
|
894
|
-
targetContainer.splice(parentIndexInGrandparent + 1, 0, movedItem);
|
|
895
|
-
// Update signal to reflect the change
|
|
896
|
-
this.menuItems.set([...this.menuItems()]);
|
|
897
|
-
// Save both containers
|
|
898
|
-
const parentName = parent?.name ?? null;
|
|
899
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), container, parentName);
|
|
900
|
-
const grandParentName = grandParentResult?.parent?.name ?? null;
|
|
901
|
-
await this.menuManagementService.reorderMenuItems(this.currentScope(), targetContainer, grandParentName);
|
|
902
|
-
this.toastService.success('@application-management:menu-management.messages.reorder-success');
|
|
903
|
-
}
|
|
904
|
-
catch (error) {
|
|
905
|
-
this.toastService.danger('@application-management:menu-management.messages.reorder-error');
|
|
906
|
-
await this.loadMenuItems();
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
//#endregion
|
|
910
|
-
//#region ---- Dialog Helpers ----
|
|
911
|
-
/**
|
|
912
|
-
* Show menu item dialog (add/edit)
|
|
913
|
-
*/
|
|
914
|
-
async showMenuItemDialog(item, parent) {
|
|
915
|
-
const isEdit = !!item;
|
|
916
|
-
const title = isEdit
|
|
917
|
-
? '@application-management:menu-management.edit-dialog.title'
|
|
918
|
-
: '@application-management:menu-management.add-dialog.title';
|
|
919
|
-
const context = {
|
|
920
|
-
name: item?.name || '',
|
|
921
|
-
text: item?.text || '',
|
|
922
|
-
icon: item?.icon || '',
|
|
923
|
-
path: item?.path || '',
|
|
924
|
-
description: item?.description || '',
|
|
925
|
-
};
|
|
926
|
-
const dialogRef = await this.layoutBuilder
|
|
927
|
-
.create()
|
|
928
|
-
.dialog((dialog) => {
|
|
929
|
-
dialog
|
|
930
|
-
.setTitle(title)
|
|
931
|
-
.setContext(context)
|
|
932
|
-
.content((flex) => {
|
|
933
|
-
flex
|
|
934
|
-
.setDirection('column')
|
|
935
|
-
.formField('@application-management:menu-management.fields.name', (field) => {
|
|
936
|
-
field.path('name');
|
|
937
|
-
field.textBox({
|
|
938
|
-
placeholder: '@application-management:menu-management.fields.name',
|
|
939
|
-
validations: [{ rule: 'required' }],
|
|
940
|
-
disabled: isEdit && item?.isBuiltIn,
|
|
941
|
-
});
|
|
942
|
-
})
|
|
943
|
-
.formField('@application-management:menu-management.fields.text', (field) => {
|
|
944
|
-
field.path('text');
|
|
945
|
-
field.textBox({
|
|
946
|
-
placeholder: '@application-management:menu-management.fields.text',
|
|
947
|
-
validations: [{ rule: 'required' }],
|
|
948
|
-
});
|
|
949
|
-
})
|
|
950
|
-
.formField('@application-management:menu-management.fields.icon', (field) => {
|
|
951
|
-
field.path('icon');
|
|
952
|
-
field.customWidget('icon-chooser', {});
|
|
953
|
-
})
|
|
954
|
-
.formField('@application-management:menu-management.fields.path', (field) => {
|
|
955
|
-
field.path('path');
|
|
956
|
-
field.textBox({
|
|
957
|
-
placeholder: '@application-management:menu-management.fields.path',
|
|
958
|
-
disabled: isEdit && item?.isBuiltIn,
|
|
959
|
-
});
|
|
960
|
-
})
|
|
961
|
-
.formField('@application-management:menu-management.fields.description', (field) => {
|
|
962
|
-
field.path('description');
|
|
963
|
-
field.largeTextBox({
|
|
964
|
-
placeholder: '@application-management:menu-management.fields.description',
|
|
965
|
-
rows: 3,
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
})
|
|
969
|
-
.setActions((actions) => {
|
|
970
|
-
actions.cancel('@general:actions.cancel.title').submit('@general:actions.save.title');
|
|
971
|
-
});
|
|
972
|
-
})
|
|
973
|
-
.show();
|
|
974
|
-
const action = dialogRef.action();
|
|
975
|
-
if (action === 'cancel') {
|
|
976
|
-
dialogRef.close();
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
const formData = dialogRef.context();
|
|
980
|
-
try {
|
|
981
|
-
if (isEdit && item?.name) {
|
|
982
|
-
// Update existing item
|
|
983
|
-
if (item.isCustom) {
|
|
984
|
-
await this.menuManagementService.updateCustomMenuItem(this.currentScope(), item.name, formData);
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
await this.menuManagementService.updateMenuProperties(this.currentScope(), item.name, formData);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
// Add new custom item
|
|
992
|
-
const newItem = {
|
|
993
|
-
name: formData.name,
|
|
994
|
-
text: formData.text,
|
|
995
|
-
icon: formData.icon,
|
|
996
|
-
path: formData.path,
|
|
997
|
-
priority: formData.priority,
|
|
998
|
-
description: formData.description,
|
|
999
|
-
};
|
|
1000
|
-
// If parent is specified, add as child
|
|
1001
|
-
if (parent?.name) {
|
|
1002
|
-
// Add custom item as child of parent
|
|
1003
|
-
await this.menuManagementService.addCustomMenuItemAsChild(this.currentScope(), newItem, parent.name);
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
// Add as root level item
|
|
1007
|
-
await this.menuManagementService.addCustomMenuItem(this.currentScope(), newItem);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
this.toastService.success('@application-management:menu-management.messages.save-success');
|
|
1011
|
-
await this.loadMenuItems();
|
|
1012
|
-
dialogRef.close();
|
|
1013
|
-
}
|
|
1014
|
-
catch (error) {
|
|
1015
|
-
this.toastService.danger('@application-management:menu-management.messages.save-error');
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1019
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.4", type: AXMMenuListComponent, isStandalone: true, selector: "axm-menu-list", host: { classAttribute: "axm-menu-list-page" }, providers: [
|
|
1020
|
-
{
|
|
1021
|
-
provide: AXPPageLayoutBase,
|
|
1022
|
-
useExisting: AXMMenuListComponent,
|
|
1023
|
-
},
|
|
1024
|
-
], usesInheritance: true, ngImport: i0, template: "<axp-page-layout>\n <axp-page-content>\n @if (isLoading()) {\n <!-- Loading State -->\n <axp-state-message\n mode=\"loading\"\n icon=\"fa-light fa-spinner-third fa-spin\"\n [title]=\"('@application-management:menu-management.loading.title' | translate | async) || 'Loading...'\"\n [description]=\"\n ('@application-management:menu-management.loading.description' | translate | async) || 'Please wait'\n \"\n />\n } @else if (error()) {\n <!-- Error State -->\n <axp-state-message\n mode=\"error\"\n icon=\"fa-light fa-circle-exclamation\"\n [title]=\"('@application-management:menu-management.error.title' | translate | async) || 'Error'\"\n [description]=\"error() || 'An error occurred'\"\n >\n <ax-button\n slot=\"actions\"\n [text]=\"'@general:actions.retry.title' | translate | async\"\n [look]=\"'outline'\"\n [color]=\"'primary'\"\n (onClick)=\"loadMenuItems()\"\n >\n <i class=\"fa-light fa-rotate-left\"></i>\n </ax-button>\n </axp-state-message>\n } @else if (menuItems().length === 0) {\n <!-- Empty State -->\n <axp-state-message\n mode=\"empty\"\n icon=\"fa-light fa-bars\"\n [title]=\"('@application-management:menu-management.empty-state.title' | translate | async) || 'No menu items'\"\n [description]=\"\n ('@application-management:menu-management.empty-state.description' | translate | async) ||\n 'Add a new menu item to get started'\n \"\n >\n <ax-button\n slot=\"actions\"\n [text]=\"'@application-management:menu-management.actions.add-root' | translate | async\"\n [look]=\"'solid'\"\n [color]=\"'primary'\"\n (onClick)=\"addRootMenuItem()\"\n >\n <i class=\"fa-light fa-plus\"></i>\n </ax-button>\n </axp-state-message>\n } @else {\n <!-- Menu Tree Content -->\n <div class=\"axm-menu-list__container\">\n <!-- Menu Tree -->\n <div\n class=\"axm-menu-list__tree\"\n cdkDropList\n id=\"menu-root\"\n [cdkDropListData]=\"menuItems()\"\n [cdkDropListConnectedTo]=\"allDropListIds()\"\n (cdkDropListDropped)=\"onItemDropped($event)\"\n >\n @for (item of menuItems(); track item.name || item.path || $index; let i = $index) {\n <axm-menu-tree-item\n [item]=\"item\"\n [level]=\"0\"\n [canDelete]=\"true\"\n [connectedDropLists]=\"allDropListIds()\"\n [canMoveUp]=\"i > 0\"\n [canMoveDown]=\"i < menuItems().length - 1\"\n [canMoveToParent]=\"false\"\n (actionTriggered)=\"onActionTriggered($event)\"\n (itemDropped)=\"onItemDropped($event)\"\n />\n }\n </div>\n </div>\n }\n </axp-page-content>\n</axp-page-layout>\n", styles: [".axm-menu-list-page .axm-menu-list__container{display:flex;flex-direction:column;gap:1rem;padding:1.5rem}.axm-menu-list-page .axm-menu-list__info-banner{display:flex;align-items:center;gap:.75rem;border-radius:.375rem;padding:1rem;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.axm-menu-list-page .axm-menu-list__info-banner i{font-size:1.125rem;line-height:1.75rem}.axm-menu-list-page .axm-menu-list__info-banner span{font-size:.875rem;line-height:1.25rem}.axm-menu-list-page .axm-menu-list__tree{display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPPageLayoutComponent, selector: "axp-page-layout" }, { kind: "component", type: AXPThemeLayoutBlockComponent, selector: " axp-page-content, axp-page-footer-container, axp-page-footer, axp-page-header, axp-page-header-container, axp-page-toolbar, axp-layout-content, axp-layout-page-content, axp-layout-sections, axp-layout-body, axp-layout-page-body, axp-layout-prefix, axp-layout-suffix, axp-layout-title-bar, axp-layout-title, axp-layout-title-actions, axp-layout-nav-button, axp-layout-description, axp-layout-breadcrumbs, axp-layout-list-action, " }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "variant"] }, { kind: "component", type: AXMMenuTreeItemComponent, selector: "axm-menu-tree-item", inputs: ["item", "level", "canDelete", "connectedDropLists", "canMoveUp", "canMoveDown", "canMoveToParent"], outputs: ["actionTriggered", "itemDropped"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1025
|
-
}
|
|
1026
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuListComponent, decorators: [{
|
|
1027
|
-
type: Component,
|
|
1028
|
-
args: [{ selector: 'axm-menu-list', standalone: true, imports: [
|
|
1029
|
-
CommonModule,
|
|
1030
|
-
CdkDropList,
|
|
1031
|
-
AXButtonModule,
|
|
1032
|
-
AXTranslationModule,
|
|
1033
|
-
AXPPageLayoutComponent,
|
|
1034
|
-
AXPThemeLayoutBlockComponent,
|
|
1035
|
-
AXPStateMessageComponent,
|
|
1036
|
-
AXMMenuTreeItemComponent,
|
|
1037
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
|
|
1038
|
-
{
|
|
1039
|
-
provide: AXPPageLayoutBase,
|
|
1040
|
-
useExisting: AXMMenuListComponent,
|
|
1041
|
-
},
|
|
1042
|
-
], host: { class: 'axm-menu-list-page' }, template: "<axp-page-layout>\n <axp-page-content>\n @if (isLoading()) {\n <!-- Loading State -->\n <axp-state-message\n mode=\"loading\"\n icon=\"fa-light fa-spinner-third fa-spin\"\n [title]=\"('@application-management:menu-management.loading.title' | translate | async) || 'Loading...'\"\n [description]=\"\n ('@application-management:menu-management.loading.description' | translate | async) || 'Please wait'\n \"\n />\n } @else if (error()) {\n <!-- Error State -->\n <axp-state-message\n mode=\"error\"\n icon=\"fa-light fa-circle-exclamation\"\n [title]=\"('@application-management:menu-management.error.title' | translate | async) || 'Error'\"\n [description]=\"error() || 'An error occurred'\"\n >\n <ax-button\n slot=\"actions\"\n [text]=\"'@general:actions.retry.title' | translate | async\"\n [look]=\"'outline'\"\n [color]=\"'primary'\"\n (onClick)=\"loadMenuItems()\"\n >\n <i class=\"fa-light fa-rotate-left\"></i>\n </ax-button>\n </axp-state-message>\n } @else if (menuItems().length === 0) {\n <!-- Empty State -->\n <axp-state-message\n mode=\"empty\"\n icon=\"fa-light fa-bars\"\n [title]=\"('@application-management:menu-management.empty-state.title' | translate | async) || 'No menu items'\"\n [description]=\"\n ('@application-management:menu-management.empty-state.description' | translate | async) ||\n 'Add a new menu item to get started'\n \"\n >\n <ax-button\n slot=\"actions\"\n [text]=\"'@application-management:menu-management.actions.add-root' | translate | async\"\n [look]=\"'solid'\"\n [color]=\"'primary'\"\n (onClick)=\"addRootMenuItem()\"\n >\n <i class=\"fa-light fa-plus\"></i>\n </ax-button>\n </axp-state-message>\n } @else {\n <!-- Menu Tree Content -->\n <div class=\"axm-menu-list__container\">\n <!-- Menu Tree -->\n <div\n class=\"axm-menu-list__tree\"\n cdkDropList\n id=\"menu-root\"\n [cdkDropListData]=\"menuItems()\"\n [cdkDropListConnectedTo]=\"allDropListIds()\"\n (cdkDropListDropped)=\"onItemDropped($event)\"\n >\n @for (item of menuItems(); track item.name || item.path || $index; let i = $index) {\n <axm-menu-tree-item\n [item]=\"item\"\n [level]=\"0\"\n [canDelete]=\"true\"\n [connectedDropLists]=\"allDropListIds()\"\n [canMoveUp]=\"i > 0\"\n [canMoveDown]=\"i < menuItems().length - 1\"\n [canMoveToParent]=\"false\"\n (actionTriggered)=\"onActionTriggered($event)\"\n (itemDropped)=\"onItemDropped($event)\"\n />\n }\n </div>\n </div>\n }\n </axp-page-content>\n</axp-page-layout>\n", styles: [".axm-menu-list-page .axm-menu-list__container{display:flex;flex-direction:column;gap:1rem;padding:1.5rem}.axm-menu-list-page .axm-menu-list__info-banner{display:flex;align-items:center;gap:.75rem;border-radius:.375rem;padding:1rem;--tw-bg-opacity: 1;background-color:rgba(var(--ax-sys-color-primary-500),var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.axm-menu-list-page .axm-menu-list__info-banner i{font-size:1.125rem;line-height:1.75rem}.axm-menu-list-page .axm-menu-list__info-banner span{font-size:.875rem;line-height:1.25rem}.axm-menu-list-page .axm-menu-list__tree{display:flex;flex-direction:column;gap:.5rem}\n"] }]
|
|
1043
|
-
}] });
|
|
1044
|
-
|
|
1045
|
-
export { AXMMenuListComponent };
|
|
1046
|
-
//# sourceMappingURL=acorex-modules-application-management-menu-list.component-BMbl5rtn.mjs.map
|