@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
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import * as i4 from '@acorex/components/badge';
|
|
2
|
+
import { AXBadgeModule } from '@acorex/components/badge';
|
|
3
|
+
import * as i1 from '@acorex/components/button';
|
|
4
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
5
|
+
import * as i2 from '@acorex/components/decorators';
|
|
6
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
7
|
+
import { AXDialogService } from '@acorex/components/dialog';
|
|
8
|
+
import * as i3 from '@acorex/components/dropdown';
|
|
9
|
+
import { AXDropdownModule } from '@acorex/components/dropdown';
|
|
10
|
+
import { AXDropdownButtonModule } from '@acorex/components/dropdown-button';
|
|
11
|
+
import { AXToastService } from '@acorex/components/toast';
|
|
12
|
+
import { AXTree2Component } from '@acorex/components/tree2';
|
|
13
|
+
import * as i6 from '@acorex/core/translation';
|
|
14
|
+
import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
15
|
+
import { AXPPlatformScope } from '@acorex/platform/core';
|
|
16
|
+
import { AXPLayoutBuilderService } from '@acorex/platform/layout/builder';
|
|
17
|
+
import { AXPThemeLayoutBlockComponent, AXPStateMessageComponent } from '@acorex/platform/layout/components';
|
|
18
|
+
import { AXPPageLayoutBaseComponent, AXPPageLayoutComponent, AXPPageLayoutBase } from '@acorex/platform/layout/views';
|
|
19
|
+
import * as i5 from '@angular/common';
|
|
20
|
+
import { CommonModule } from '@angular/common';
|
|
21
|
+
import * as i0 from '@angular/core';
|
|
22
|
+
import { inject, Injectable, viewChild, signal, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
23
|
+
import { ActivatedRoute } from '@angular/router';
|
|
24
|
+
import { Subject, takeUntil } from 'rxjs';
|
|
25
|
+
import { AXPSettingService, AXPMenuProviderService } from '@acorex/platform/common';
|
|
26
|
+
import { cloneDeep } from 'lodash-es';
|
|
27
|
+
import { A as AXP_MENU_CUSTOMIZATION_KEY, a as AXP_MENU_CUSTOMIZATION_DEFAULT } from './acorex-modules-application-management-acorex-modules-application-management-BqYLpEvY.mjs';
|
|
28
|
+
|
|
29
|
+
//#region ---- Menu Management Service ----
|
|
30
|
+
/**
|
|
31
|
+
* Constants for priority calculation
|
|
32
|
+
*/
|
|
33
|
+
const PRIORITY_BASE_ROOT = 0;
|
|
34
|
+
const PRIORITY_BASE_NESTED = 1000;
|
|
35
|
+
const PRIORITY_STEP = 10;
|
|
36
|
+
class AXPMenuManagementService {
|
|
37
|
+
constructor() {
|
|
38
|
+
//#region ---- Dependencies ----
|
|
39
|
+
this.settingService = inject(AXPSettingService);
|
|
40
|
+
this.menuProviderService = inject(AXPMenuProviderService);
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region ---- Public Methods ----
|
|
44
|
+
/**
|
|
45
|
+
* Get menu tree as AXTreeNode[] for ax-tree2 component
|
|
46
|
+
*/
|
|
47
|
+
async getMenuTree(scope) {
|
|
48
|
+
// Get RAW menu items WITHOUT middleware (for management purposes)
|
|
49
|
+
const baseItems = await this.menuProviderService.rawItems();
|
|
50
|
+
// Load customizations for the given scope
|
|
51
|
+
const customization = await this.loadCustomization(scope);
|
|
52
|
+
// Merge custom items with base items
|
|
53
|
+
const allItems = [...baseItems, ...customization.customItems];
|
|
54
|
+
// Apply parent overrides to restructure items BEFORE building tree
|
|
55
|
+
const restructuredItems = this.applyParentOverrides(allItems, customization);
|
|
56
|
+
// Convert to tree nodes with metadata
|
|
57
|
+
return this.convertToTreeNodes(restructuredItems, customization);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sync tree changes back to customization
|
|
61
|
+
* Called automatically when tree structure changes via drag-drop
|
|
62
|
+
*/
|
|
63
|
+
async syncTreeChanges(scope, treeNodes) {
|
|
64
|
+
const customization = await this.loadCustomization(scope);
|
|
65
|
+
// Extract and update priorities and parent relationships from tree structure
|
|
66
|
+
this.updateCustomizationFromTree(treeNodes, customization);
|
|
67
|
+
await this.saveCustomization(scope, customization);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Load menu customization for a specific scope
|
|
71
|
+
*/
|
|
72
|
+
async loadCustomization(scope) {
|
|
73
|
+
try {
|
|
74
|
+
const scopedSettings = this.settingService.scope(scope);
|
|
75
|
+
const saved = await scopedSettings.get(AXP_MENU_CUSTOMIZATION_KEY);
|
|
76
|
+
if (saved && saved.version) {
|
|
77
|
+
return saved;
|
|
78
|
+
}
|
|
79
|
+
return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.warn('Failed to load menu customization, using defaults', error);
|
|
83
|
+
return cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Save menu customization for a specific scope
|
|
88
|
+
*/
|
|
89
|
+
async saveCustomization(scope, customization) {
|
|
90
|
+
const scopedSettings = this.settingService.scope(scope);
|
|
91
|
+
await scopedSettings.set(AXP_MENU_CUSTOMIZATION_KEY, customization);
|
|
92
|
+
// Clear menu cache to force reload
|
|
93
|
+
this.menuProviderService.clearCache();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Hide a menu item
|
|
97
|
+
*/
|
|
98
|
+
async hideMenuItem(scope, menuName) {
|
|
99
|
+
this.validateMenuName(menuName, 'hide');
|
|
100
|
+
const customization = await this.loadCustomization(scope);
|
|
101
|
+
this.ensureOverrideExists(customization, menuName);
|
|
102
|
+
customization.overrides[menuName].hidden = true;
|
|
103
|
+
await this.saveCustomization(scope, customization);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Show a menu item
|
|
107
|
+
*/
|
|
108
|
+
async showMenuItem(scope, menuName) {
|
|
109
|
+
this.validateMenuName(menuName, 'show');
|
|
110
|
+
const customization = await this.loadCustomization(scope);
|
|
111
|
+
if (customization.overrides[menuName]) {
|
|
112
|
+
delete customization.overrides[menuName].hidden;
|
|
113
|
+
this.cleanupEmptyOverride(customization, menuName);
|
|
114
|
+
}
|
|
115
|
+
await this.saveCustomization(scope, customization);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Update menu item priority
|
|
119
|
+
*/
|
|
120
|
+
async updateMenuPriority(scope, menuName, priority) {
|
|
121
|
+
this.validateMenuName(menuName, 'update priority for');
|
|
122
|
+
const customization = await this.loadCustomization(scope);
|
|
123
|
+
this.ensureOverrideExists(customization, menuName);
|
|
124
|
+
customization.overrides[menuName].priority = priority;
|
|
125
|
+
await this.saveCustomization(scope, customization);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Update menu item properties
|
|
129
|
+
*/
|
|
130
|
+
async updateMenuProperties(scope, menuName, properties) {
|
|
131
|
+
this.validateMenuName(menuName, 'update properties for');
|
|
132
|
+
const customization = await this.loadCustomization(scope);
|
|
133
|
+
this.ensureOverrideExists(customization, menuName);
|
|
134
|
+
customization.overrides[menuName].properties = {
|
|
135
|
+
...customization.overrides[menuName].properties,
|
|
136
|
+
...properties,
|
|
137
|
+
};
|
|
138
|
+
await this.saveCustomization(scope, customization);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Add custom menu item
|
|
142
|
+
*/
|
|
143
|
+
async addCustomMenuItem(scope, menuItem, parent) {
|
|
144
|
+
const customization = await this.loadCustomization(scope);
|
|
145
|
+
// Generate unique name if not provided
|
|
146
|
+
if (!menuItem.name) {
|
|
147
|
+
menuItem.name = `custom-menu-${Date.now()}`;
|
|
148
|
+
}
|
|
149
|
+
customization.customItems.push(menuItem);
|
|
150
|
+
await this.saveCustomization(scope, customization);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Update custom menu item
|
|
154
|
+
*/
|
|
155
|
+
async updateCustomMenuItem(scope, menuName, menuItem) {
|
|
156
|
+
this.validateMenuName(menuName, 'update');
|
|
157
|
+
const customization = await this.loadCustomization(scope);
|
|
158
|
+
const index = customization.customItems.findIndex((item) => item.name === menuName);
|
|
159
|
+
if (index === -1) {
|
|
160
|
+
throw new Error(`Custom menu item '${menuName}' not found`);
|
|
161
|
+
}
|
|
162
|
+
customization.customItems[index] = menuItem;
|
|
163
|
+
await this.saveCustomization(scope, customization);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Delete custom menu item
|
|
167
|
+
*/
|
|
168
|
+
async deleteCustomMenuItem(scope, menuName) {
|
|
169
|
+
this.validateMenuName(menuName, 'delete');
|
|
170
|
+
const customization = await this.loadCustomization(scope);
|
|
171
|
+
const initialLength = customization.customItems.length;
|
|
172
|
+
customization.customItems = customization.customItems.filter((item) => item.name !== menuName);
|
|
173
|
+
if (customization.customItems.length === initialLength) {
|
|
174
|
+
throw new Error(`Custom menu item '${menuName}' not found`);
|
|
175
|
+
}
|
|
176
|
+
await this.saveCustomization(scope, customization);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Reset all customizations for a scope
|
|
180
|
+
*/
|
|
181
|
+
async resetCustomizations(scope) {
|
|
182
|
+
await this.saveCustomization(scope, cloneDeep(AXP_MENU_CUSTOMIZATION_DEFAULT));
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region ---- Private Methods ----
|
|
186
|
+
/**
|
|
187
|
+
* Apply parent overrides from customization to restructure menu items
|
|
188
|
+
* This ensures the tree reflects saved parent changes (moves)
|
|
189
|
+
*/
|
|
190
|
+
applyParentOverrides(items, customization) {
|
|
191
|
+
// First, flatten all items into a map for easy lookup
|
|
192
|
+
const itemMap = this.flattenMenuItems(items);
|
|
193
|
+
// Build new structure based on effective parent relationships
|
|
194
|
+
const rootItems = [];
|
|
195
|
+
const childrenMap = new Map();
|
|
196
|
+
for (const [itemName, item] of itemMap.entries()) {
|
|
197
|
+
const override = customization.overrides[itemName];
|
|
198
|
+
// Determine effective parent (override takes precedence)
|
|
199
|
+
let effectiveParent;
|
|
200
|
+
if (override?.parentName !== undefined) {
|
|
201
|
+
// parentName is explicitly set (could be null for root, or a parent name)
|
|
202
|
+
effectiveParent = override.parentName;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// No override - find original parent by searching original structure
|
|
206
|
+
effectiveParent = this.findOriginalParent(items, itemName);
|
|
207
|
+
}
|
|
208
|
+
if (effectiveParent === null || effectiveParent === undefined) {
|
|
209
|
+
// Root level item
|
|
210
|
+
rootItems.push(item);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Child item - add to parent's children
|
|
214
|
+
if (!childrenMap.has(effectiveParent)) {
|
|
215
|
+
childrenMap.set(effectiveParent, []);
|
|
216
|
+
}
|
|
217
|
+
childrenMap.get(effectiveParent).push(item);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Attach children to their parents recursively
|
|
221
|
+
const attachChildren = (item) => {
|
|
222
|
+
if (item.name && childrenMap.has(item.name)) {
|
|
223
|
+
item.children = childrenMap.get(item.name);
|
|
224
|
+
item.children?.forEach(attachChildren);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
rootItems.forEach(attachChildren);
|
|
228
|
+
return rootItems;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Find the original parent name of an item in the original structure
|
|
232
|
+
* Returns undefined for root items, string for items with parents, null for not found
|
|
233
|
+
*/
|
|
234
|
+
findOriginalParent(items, targetName, currentParent) {
|
|
235
|
+
for (const item of items) {
|
|
236
|
+
if (item.name === targetName) {
|
|
237
|
+
return currentParent; // undefined if root, string if has parent
|
|
238
|
+
}
|
|
239
|
+
if (item.children) {
|
|
240
|
+
const found = this.findOriginalParent(item.children, targetName, item.name);
|
|
241
|
+
if (found !== null) {
|
|
242
|
+
// Found in children (could be undefined for root of subtree, or string for nested item)
|
|
243
|
+
return found;
|
|
244
|
+
}
|
|
245
|
+
// found === null means not found in this subtree, continue searching
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null; // Not found in this level or any children
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Convert menu items to AXTreeNode[] with metadata
|
|
252
|
+
*/
|
|
253
|
+
convertToTreeNodes(items, customization, parentName) {
|
|
254
|
+
const nodes = items.map((item) => {
|
|
255
|
+
const itemName = item.name;
|
|
256
|
+
const override = itemName ? customization.overrides[itemName] : undefined;
|
|
257
|
+
const isCustom = itemName ? customization.customItems.some((ci) => ci.name === itemName) : false;
|
|
258
|
+
// Create metadata
|
|
259
|
+
const metadata = {
|
|
260
|
+
isBuiltIn: !isCustom,
|
|
261
|
+
isCustom,
|
|
262
|
+
isHidden: override?.hidden || false,
|
|
263
|
+
originalPriority: item.priority,
|
|
264
|
+
originalParentName: parentName,
|
|
265
|
+
};
|
|
266
|
+
// Apply priority override
|
|
267
|
+
const effectivePriority = override?.priority !== undefined ? override.priority : item.priority;
|
|
268
|
+
const itemWithPriority = { ...item, priority: effectivePriority };
|
|
269
|
+
// Create tree node data
|
|
270
|
+
const nodeData = {
|
|
271
|
+
menuItem: itemWithPriority,
|
|
272
|
+
metadata,
|
|
273
|
+
};
|
|
274
|
+
// Create tree node
|
|
275
|
+
const treeNode = {
|
|
276
|
+
id: itemName || item.path || this.generateNodeId(),
|
|
277
|
+
label: item.text || item.path || 'Unnamed',
|
|
278
|
+
icon: item.icon,
|
|
279
|
+
expanded: false,
|
|
280
|
+
selected: false,
|
|
281
|
+
visible: true, // Always visible in management interface, CSS handles hidden styling
|
|
282
|
+
children: item.children ? this.convertToTreeNodes(item.children, customization, itemName) : undefined,
|
|
283
|
+
childrenCount: item.children?.length,
|
|
284
|
+
data: nodeData,
|
|
285
|
+
};
|
|
286
|
+
return treeNode;
|
|
287
|
+
});
|
|
288
|
+
// Sort by priority
|
|
289
|
+
nodes.sort((a, b) => {
|
|
290
|
+
const aPriority = a.data?.menuItem.priority ?? 0;
|
|
291
|
+
const bPriority = b.data?.menuItem.priority ?? 0;
|
|
292
|
+
return aPriority - bPriority;
|
|
293
|
+
});
|
|
294
|
+
return nodes;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Update customization from tree structure
|
|
298
|
+
* Extracts priorities and parent relationships from tree nodes
|
|
299
|
+
*/
|
|
300
|
+
updateCustomizationFromTree(treeNodes, customization, parentName) {
|
|
301
|
+
// Calculate base priority for this level to maintain relative ordering
|
|
302
|
+
const basePriority = parentName ? PRIORITY_BASE_NESTED : PRIORITY_BASE_ROOT;
|
|
303
|
+
const priorityStep = PRIORITY_STEP;
|
|
304
|
+
treeNodes.forEach((node, index) => {
|
|
305
|
+
const nodeData = node.data;
|
|
306
|
+
const menuItem = nodeData?.menuItem;
|
|
307
|
+
if (menuItem?.name) {
|
|
308
|
+
this.ensureOverrideExists(customization, menuItem.name);
|
|
309
|
+
// Calculate new priority based on current position
|
|
310
|
+
const newPriority = basePriority + index * priorityStep;
|
|
311
|
+
// Get current effective priority (considering existing overrides)
|
|
312
|
+
const currentEffectivePriority = customization.overrides[menuItem.name]?.priority ?? nodeData.metadata.originalPriority ?? 0;
|
|
313
|
+
// Always update priority to reflect current position in tree
|
|
314
|
+
// This ensures reordering works correctly even after multiple moves
|
|
315
|
+
if (newPriority !== currentEffectivePriority) {
|
|
316
|
+
customization.overrides[menuItem.name].priority = newPriority;
|
|
317
|
+
}
|
|
318
|
+
// Determine current effective parent (considering existing overrides)
|
|
319
|
+
const currentEffectiveParent = customization.overrides[menuItem.name]?.parentName ?? nodeData.metadata.originalParentName;
|
|
320
|
+
// Update parent relationship if it changed from current effective parent
|
|
321
|
+
if (parentName !== currentEffectiveParent) {
|
|
322
|
+
if (parentName) {
|
|
323
|
+
customization.overrides[menuItem.name].parentName = parentName;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
// Moving to root - set to null (not undefined) so middleware will process it
|
|
327
|
+
// moveTo(null) moves the item to root level
|
|
328
|
+
customization.overrides[menuItem.name].parentName = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Clean up empty overrides
|
|
332
|
+
this.cleanupEmptyOverride(customization, menuItem.name);
|
|
333
|
+
// Recursively process children
|
|
334
|
+
if (node.children && node.children.length > 0) {
|
|
335
|
+
this.updateCustomizationFromTree(node.children, customization, menuItem.name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Validate menu name before operations
|
|
342
|
+
*/
|
|
343
|
+
validateMenuName(menuName, operation) {
|
|
344
|
+
if (!menuName) {
|
|
345
|
+
throw new Error(`Menu item must have a name to be ${operation}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Ensure override object exists for a menu item
|
|
350
|
+
*/
|
|
351
|
+
ensureOverrideExists(customization, menuName) {
|
|
352
|
+
if (!customization.overrides[menuName]) {
|
|
353
|
+
customization.overrides[menuName] = {};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Clean up empty override objects
|
|
358
|
+
*/
|
|
359
|
+
cleanupEmptyOverride(customization, menuName) {
|
|
360
|
+
if (Object.keys(customization.overrides[menuName] || {}).length === 0) {
|
|
361
|
+
delete customization.overrides[menuName];
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Flatten menu items recursively into a map
|
|
366
|
+
*/
|
|
367
|
+
flattenMenuItems(items) {
|
|
368
|
+
const itemMap = new Map();
|
|
369
|
+
const flatten = (itemList) => {
|
|
370
|
+
for (const item of itemList) {
|
|
371
|
+
if (item.name) {
|
|
372
|
+
// Clone item without children to rebuild structure
|
|
373
|
+
itemMap.set(item.name, { ...item, children: undefined });
|
|
374
|
+
}
|
|
375
|
+
if (item.children) {
|
|
376
|
+
flatten(item.children);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
flatten(items);
|
|
381
|
+
return itemMap;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Generate unique ID for tree nodes
|
|
385
|
+
*/
|
|
386
|
+
generateNodeId() {
|
|
387
|
+
return `node-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
388
|
+
}
|
|
389
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
390
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, providedIn: 'root' }); }
|
|
391
|
+
}
|
|
392
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXPMenuManagementService, decorators: [{
|
|
393
|
+
type: Injectable,
|
|
394
|
+
args: [{ providedIn: 'root' }]
|
|
395
|
+
}] });
|
|
396
|
+
|
|
397
|
+
//#region ---- Menu List Component ----
|
|
398
|
+
class AXMMenuListComponent extends AXPPageLayoutBaseComponent {
|
|
399
|
+
constructor() {
|
|
400
|
+
super(...arguments);
|
|
401
|
+
//#region ---- Dependencies ----
|
|
402
|
+
this.menuManagementService = inject(AXPMenuManagementService);
|
|
403
|
+
this.layoutBuilder = inject(AXPLayoutBuilderService);
|
|
404
|
+
this.dialogService = inject(AXDialogService);
|
|
405
|
+
this.toastService = inject(AXToastService);
|
|
406
|
+
this.translationService = inject(AXTranslationService);
|
|
407
|
+
this.route = inject(ActivatedRoute);
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region ---- Component State ----
|
|
410
|
+
this.tree = viewChild(AXTree2Component, ...(ngDevMode ? [{ debugName: "tree" }] : []));
|
|
411
|
+
this.treeNodes = signal([], ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
|
|
412
|
+
this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
413
|
+
this.error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
414
|
+
this.currentScope = signal(AXPPlatformScope.User, ...(ngDevMode ? [{ debugName: "currentScope" }] : []));
|
|
415
|
+
this.isSyncing = false; // Prevent concurrent sync operations
|
|
416
|
+
this.destroy$ = new Subject(); // For subscription cleanup
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region ---- Lifecycle ----
|
|
420
|
+
async ngOnInit() {
|
|
421
|
+
await super.ngOnInit();
|
|
422
|
+
// Get scope from route (with memory leak prevention)
|
|
423
|
+
this.route.params.pipe(takeUntil(this.destroy$)).subscribe(async (params) => {
|
|
424
|
+
const scope = params['scope'];
|
|
425
|
+
if (scope) {
|
|
426
|
+
this.currentScope.set(scope);
|
|
427
|
+
await this.loadMenuItems();
|
|
428
|
+
this.recompute(); // Update page header after loading
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
ngOnDestroy() {
|
|
433
|
+
this.destroy$.next();
|
|
434
|
+
this.destroy$.complete();
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region ---- Event Handlers ----
|
|
438
|
+
/**
|
|
439
|
+
* Handle before drop validation
|
|
440
|
+
*/
|
|
441
|
+
handleBeforeDrop(e) {
|
|
442
|
+
const targetParent = e.currentParent?.data;
|
|
443
|
+
// Prevent dropping into items with paths (leaf nodes)
|
|
444
|
+
if (targetParent?.menuItem?.path) {
|
|
445
|
+
e.canceled = true;
|
|
446
|
+
this.toastService.warning('@application-management:menu-management.messages.move-error');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
// Note: Circular reference validation is handled by the tree component itself
|
|
450
|
+
// Additional validation can be added here if needed based on business rules
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Handle after drop event to sync changes
|
|
454
|
+
*/
|
|
455
|
+
async handleAfterDrop() {
|
|
456
|
+
await this.syncTreeChanges();
|
|
457
|
+
}
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region ---- Data Loading ----
|
|
460
|
+
/**
|
|
461
|
+
* Load menu tree for current scope
|
|
462
|
+
*/
|
|
463
|
+
async loadMenuItems() {
|
|
464
|
+
try {
|
|
465
|
+
this.isLoading.set(true);
|
|
466
|
+
this.error.set('');
|
|
467
|
+
const treeNodes = await this.menuManagementService.getMenuTree(this.currentScope());
|
|
468
|
+
this.treeNodes.set(treeNodes);
|
|
469
|
+
this.recompute();
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
this.error.set(error instanceof Error ? error.message : 'Failed to load menu items');
|
|
473
|
+
this.toastService.danger('@application-management:menu-management.messages.load-error');
|
|
474
|
+
}
|
|
475
|
+
finally {
|
|
476
|
+
this.isLoading.set(false);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Sync tree changes to backend
|
|
481
|
+
*/
|
|
482
|
+
async syncTreeChanges() {
|
|
483
|
+
// Prevent concurrent sync operations
|
|
484
|
+
if (this.isSyncing) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
this.isSyncing = true;
|
|
489
|
+
const nodes = this.treeNodes();
|
|
490
|
+
const scope = this.currentScope();
|
|
491
|
+
if (nodes.length === 0) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
await this.menuManagementService.syncTreeChanges(scope, nodes);
|
|
495
|
+
this.toastService.success('@application-management:menu-management.messages.reorder-success');
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
console.error('Failed to sync tree changes:', error);
|
|
499
|
+
this.toastService.danger('@application-management:menu-management.messages.sync-error');
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
this.isSyncing = false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region ---- Page Layout Interface ----
|
|
507
|
+
/**
|
|
508
|
+
* Get page title
|
|
509
|
+
*/
|
|
510
|
+
async getPageTitle() {
|
|
511
|
+
const scopeName = this.currentScope();
|
|
512
|
+
const scopeKey = scopeName === AXPPlatformScope.User ? 'user' : 'tenant';
|
|
513
|
+
return this.translationService.translateAsync(`@application-management:menu-management.${scopeKey}.title`);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Get page description
|
|
517
|
+
*/
|
|
518
|
+
async getPageDescription() {
|
|
519
|
+
return this.translationService.translateAsync('@application-management:menu-management.description');
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get primary menu items (actions)
|
|
523
|
+
*/
|
|
524
|
+
async getPrimaryMenuItems() {
|
|
525
|
+
return [
|
|
526
|
+
{
|
|
527
|
+
title: await this.translationService.translateAsync('@application-management:menu-management.actions.add-root'),
|
|
528
|
+
icon: 'fa-light fa-plus',
|
|
529
|
+
color: 'primary',
|
|
530
|
+
command: { name: 'add-root' },
|
|
531
|
+
},
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get secondary menu items
|
|
536
|
+
*/
|
|
537
|
+
async getSecondaryMenuItems() {
|
|
538
|
+
return [
|
|
539
|
+
{
|
|
540
|
+
title: await this.translationService.translateAsync('@application-management:menu-management.actions.collapse'),
|
|
541
|
+
icon: 'fa-light fa-minus-square',
|
|
542
|
+
color: 'primary',
|
|
543
|
+
command: { name: 'collapse' },
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
title: await this.translationService.translateAsync('@application-management:menu-management.actions.expand'),
|
|
547
|
+
icon: 'fa-light fa-plus-square',
|
|
548
|
+
color: 'primary',
|
|
549
|
+
command: { name: 'expand' },
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
title: await this.translationService.translateAsync('@application-management:menu-management.actions.reset'),
|
|
553
|
+
icon: 'fa-light fa-rotate-left',
|
|
554
|
+
color: 'danger',
|
|
555
|
+
command: { name: 'reset' },
|
|
556
|
+
},
|
|
557
|
+
];
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Execute commands from page actions
|
|
561
|
+
*/
|
|
562
|
+
async execute(command) {
|
|
563
|
+
switch (command.name) {
|
|
564
|
+
case 'add-root':
|
|
565
|
+
await this.addRootMenuItem();
|
|
566
|
+
break;
|
|
567
|
+
case 'collapse':
|
|
568
|
+
this.tree()?.collapseAll();
|
|
569
|
+
break;
|
|
570
|
+
case 'expand':
|
|
571
|
+
this.tree()?.expandAll();
|
|
572
|
+
break;
|
|
573
|
+
case 'reset':
|
|
574
|
+
await this.resetCustomizations();
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region ---- Action Handlers ----
|
|
580
|
+
/**
|
|
581
|
+
* Handle menu item action
|
|
582
|
+
*/
|
|
583
|
+
async onAction(action, nodeData) {
|
|
584
|
+
switch (action) {
|
|
585
|
+
case 'show':
|
|
586
|
+
await this.showMenuItem(nodeData);
|
|
587
|
+
break;
|
|
588
|
+
case 'hide':
|
|
589
|
+
await this.hideMenuItem(nodeData);
|
|
590
|
+
break;
|
|
591
|
+
case 'edit':
|
|
592
|
+
await this.editMenuItem(nodeData);
|
|
593
|
+
break;
|
|
594
|
+
case 'delete':
|
|
595
|
+
await this.deleteMenuItem(nodeData);
|
|
596
|
+
break;
|
|
597
|
+
case 'add-child':
|
|
598
|
+
await this.addChildMenuItem(nodeData);
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Add new root menu item
|
|
604
|
+
*/
|
|
605
|
+
async addRootMenuItem() {
|
|
606
|
+
await this.showMenuItemDialog(null, null);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Add new child menu item
|
|
610
|
+
*/
|
|
611
|
+
async addChildMenuItem(parentNodeData) {
|
|
612
|
+
await this.showMenuItemDialog(null, parentNodeData.menuItem.name);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Reset customizations
|
|
616
|
+
*/
|
|
617
|
+
async resetCustomizations() {
|
|
618
|
+
const confirmed = await this.dialogService.confirm('@application-management:menu-management.reset.title', '@application-management:menu-management.reset.message');
|
|
619
|
+
if (!confirmed)
|
|
620
|
+
return;
|
|
621
|
+
try {
|
|
622
|
+
// Reset customizations for current scope
|
|
623
|
+
await this.menuManagementService.resetCustomizations(this.currentScope());
|
|
624
|
+
this.toastService.success('@application-management:menu-management.messages.reset-success');
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
console.error('Failed to reset customizations:', error);
|
|
628
|
+
this.toastService.danger('@application-management:menu-management.messages.reset-error');
|
|
629
|
+
}
|
|
630
|
+
finally {
|
|
631
|
+
// Reload anyway to show current state
|
|
632
|
+
await this.loadMenuItems();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region ---- Menu Item Actions ----
|
|
637
|
+
/**
|
|
638
|
+
* Show menu item
|
|
639
|
+
*/
|
|
640
|
+
async showMenuItem(nodeData) {
|
|
641
|
+
await this.executeMenuAction(() => this.menuManagementService.showMenuItem(this.currentScope(), nodeData.menuItem.name), '@application-management:menu-management.messages.show-success', '@application-management:menu-management.messages.show-error');
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Hide menu item
|
|
645
|
+
*/
|
|
646
|
+
async hideMenuItem(nodeData) {
|
|
647
|
+
await this.executeMenuAction(() => this.menuManagementService.hideMenuItem(this.currentScope(), nodeData.menuItem.name), '@application-management:menu-management.messages.hide-success', '@application-management:menu-management.messages.hide-error');
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Edit menu item
|
|
651
|
+
*/
|
|
652
|
+
async editMenuItem(nodeData) {
|
|
653
|
+
await this.showMenuItemDialog(nodeData);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Delete custom menu item
|
|
657
|
+
*/
|
|
658
|
+
async deleteMenuItem(nodeData) {
|
|
659
|
+
const confirmed = await this.dialogService.confirm('@general:actions.delete.title', '@application-management:menu-management.delete.message');
|
|
660
|
+
if (!confirmed)
|
|
661
|
+
return;
|
|
662
|
+
try {
|
|
663
|
+
const item = nodeData.menuItem;
|
|
664
|
+
if (!item.name)
|
|
665
|
+
return;
|
|
666
|
+
await this.menuManagementService.deleteCustomMenuItem(this.currentScope(), item.name);
|
|
667
|
+
this.toastService.success('@application-management:menu-management.messages.delete-success');
|
|
668
|
+
await this.loadMenuItems();
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
this.toastService.danger('@application-management:menu-management.messages.delete-error');
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region ---- Dialog Helpers ----
|
|
676
|
+
/**
|
|
677
|
+
* Show menu item dialog (add/edit)
|
|
678
|
+
*/
|
|
679
|
+
async showMenuItemDialog(nodeData, parentName = null) {
|
|
680
|
+
const item = nodeData?.menuItem;
|
|
681
|
+
const isEdit = !!item;
|
|
682
|
+
const title = isEdit
|
|
683
|
+
? '@application-management:menu-management.edit-dialog.title'
|
|
684
|
+
: '@application-management:menu-management.add-dialog.title';
|
|
685
|
+
const context = {
|
|
686
|
+
name: item?.name || '',
|
|
687
|
+
text: item?.text || '',
|
|
688
|
+
type: item?.type || 'menu',
|
|
689
|
+
icon: item?.icon || '',
|
|
690
|
+
path: item?.path || '',
|
|
691
|
+
description: item?.description || '',
|
|
692
|
+
};
|
|
693
|
+
const dialogRef = await this.layoutBuilder
|
|
694
|
+
.create()
|
|
695
|
+
.dialog((dialog) => {
|
|
696
|
+
dialog
|
|
697
|
+
.setTitle(title)
|
|
698
|
+
.setContext(context)
|
|
699
|
+
.content((flex) => {
|
|
700
|
+
flex
|
|
701
|
+
.setDirection('column')
|
|
702
|
+
.formField('@application-management:menu-management.fields.name', (field) => {
|
|
703
|
+
field.path('name');
|
|
704
|
+
field.textBox({
|
|
705
|
+
placeholder: '@application-management:menu-management.fields.name',
|
|
706
|
+
validations: [{ rule: 'required' }],
|
|
707
|
+
disabled: isEdit && nodeData?.metadata?.isBuiltIn,
|
|
708
|
+
});
|
|
709
|
+
})
|
|
710
|
+
.formField('@application-management:menu-management.fields.text', (field) => {
|
|
711
|
+
field.path('text');
|
|
712
|
+
field.textBox({
|
|
713
|
+
placeholder: '@application-management:menu-management.fields.text',
|
|
714
|
+
validations: [{ rule: 'required' }],
|
|
715
|
+
});
|
|
716
|
+
})
|
|
717
|
+
.formField('@application-management:menu-management.fields.icon', (field) => {
|
|
718
|
+
field.path('icon');
|
|
719
|
+
field.customWidget('icon-chooser', {});
|
|
720
|
+
})
|
|
721
|
+
.formField('@application-management:menu-management.fields.path', (field) => {
|
|
722
|
+
field.path('path');
|
|
723
|
+
field.textBox({
|
|
724
|
+
placeholder: '@application-management:menu-management.fields.path',
|
|
725
|
+
disabled: isEdit && nodeData?.metadata?.isBuiltIn,
|
|
726
|
+
});
|
|
727
|
+
})
|
|
728
|
+
.formField('@application-management:menu-management.fields.description', (field) => {
|
|
729
|
+
field.path('description');
|
|
730
|
+
field.largeTextBox({
|
|
731
|
+
placeholder: '@application-management:menu-management.fields.description',
|
|
732
|
+
rows: 3,
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
})
|
|
736
|
+
.setActions((actions) => {
|
|
737
|
+
actions.cancel('@general:actions.cancel.title').submit('@general:actions.save.title');
|
|
738
|
+
});
|
|
739
|
+
})
|
|
740
|
+
.show();
|
|
741
|
+
const action = dialogRef.action();
|
|
742
|
+
if (action === 'cancel') {
|
|
743
|
+
dialogRef.close();
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const formData = dialogRef.context();
|
|
747
|
+
try {
|
|
748
|
+
if (isEdit && item?.name) {
|
|
749
|
+
// Update existing item
|
|
750
|
+
if (nodeData?.metadata?.isCustom) {
|
|
751
|
+
await this.menuManagementService.updateCustomMenuItem(this.currentScope(), item.name, formData);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
await this.menuManagementService.updateMenuProperties(this.currentScope(), item.name, formData);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
// Add new custom item
|
|
759
|
+
const newItem = {
|
|
760
|
+
name: formData.name,
|
|
761
|
+
text: formData.text,
|
|
762
|
+
icon: formData.icon,
|
|
763
|
+
path: formData.path,
|
|
764
|
+
priority: formData.priority,
|
|
765
|
+
description: formData.description,
|
|
766
|
+
};
|
|
767
|
+
// If parent is specified, add as child
|
|
768
|
+
if (parent?.name) {
|
|
769
|
+
// Add custom item as child of parent
|
|
770
|
+
await this.menuManagementService.addCustomMenuItem(this.currentScope(), newItem, parent.name);
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
// Add as root level item
|
|
774
|
+
await this.menuManagementService.addCustomMenuItem(this.currentScope(), newItem, null);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
this.toastService.success('@application-management:menu-management.messages.save-success');
|
|
778
|
+
await this.loadMenuItems();
|
|
779
|
+
dialogRef.close();
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
this.toastService.danger('@application-management:menu-management.messages.save-error');
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Execute a menu action with common error handling
|
|
787
|
+
*/
|
|
788
|
+
async executeMenuAction(action, successMessage, errorMessage) {
|
|
789
|
+
try {
|
|
790
|
+
await action();
|
|
791
|
+
this.toastService.success(successMessage);
|
|
792
|
+
await this.loadMenuItems();
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
console.error('Menu action failed:', error);
|
|
796
|
+
this.toastService.danger(errorMessage);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
800
|
+
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: [
|
|
801
|
+
{
|
|
802
|
+
provide: AXPPageLayoutBase,
|
|
803
|
+
useExisting: AXMMenuListComponent,
|
|
804
|
+
},
|
|
805
|
+
], viewQueries: [{ propertyName: "tree", first: true, predicate: AXTree2Component, descendants: true, isSignal: true }], 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 (treeNodes().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 class=\"axm-menu-list__tree\">\n <ax-tree2\n [(nodes)]=\"treeNodes\"\n look=\"default\"\n [itemTemplate]=\"itemTemplate\"\n [showCheckbox]=\"false\"\n [showIcons]=\"false\"\n [showChildrenBadge]=\"false\"\n [nodeHeight]=\"'normal'\"\n [dragMode]=\"'handler'\"\n (onBeforeDrop)=\"handleBeforeDrop($event)\"\n (onItemsChange)=\"handleAfterDrop()\"\n ></ax-tree2>\n </div>\n </div>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Custom Item Template -->\n<ng-template #itemTemplate let-node=\"node\" let-level=\"level\">\n @let nodeData = node.data;\n @let item = nodeData.menuItem;\n @let metadata = nodeData.metadata;\n <div\n class=\"axm-menu-tree-item__wrapper\"\n [class.axm-menu-tree-item__wrapper--hidden]=\"metadata.isHidden\"\n [class.axm-menu-tree-item__wrapper--custom]=\"metadata.isCustom\"\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\">{{ item.name }}</code>\n }\n\n <ax-badge [text]=\"item.type ?? 'menu'\" color=\"primary\"></ax-badge>\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 <!-- 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', nodeData); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-edit\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Show/Hide (only for items with names) -->\n @if (item.name && metadata.isBuiltIn) {\n @if (metadata.isHidden) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.show' | translate | async)!\"\n (onClick)=\"onAction('show', nodeData); 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', nodeData); 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 @if (!item.path && (item.type === 'menu' || !item.type)) {\n <!-- Add Child -->\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.add-child' | translate | async)!\"\n (onClick)=\"onAction('add-child', nodeData); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-plus\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Delete (only for custom items with names) -->\n @if (item.name && metadata.isCustom) {\n <ax-divider></ax-divider>\n <ax-button-item\n [color]=\"'danger'\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n (onClick)=\"onAction('delete', nodeData); 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</ng-template>\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}.axm-menu-list-page .axm-menu-tree-item__wrapper{display:flex;flex:1 1 0%;align-items:center;gap:1rem;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-list-page .axm-menu-tree-item__wrapper: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-list-page .axm-menu-tree-item__wrapper--hidden{opacity:.5}.axm-menu-list-page .axm-menu-tree-item__wrapper--custom{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-list-page .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-list-page .axm-menu-tree-item__text{flex:1 1 0%;font-size:.875rem;line-height:1.25rem;font-weight:500}.axm-menu-list-page .axm-menu-tree-item__divider,.axm-menu-list-page .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-list-page .axm-menu-tree-item__divider i,.axm-menu-list-page .axm-menu-tree-item__path i{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-list-page .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-list-page .axm-menu-tree-item__actions{margin-left:.5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: AXDropdownButtonModule }, { 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: "component", type: AXTree2Component, selector: "ax-tree2", inputs: ["nodes", "showCheckbox", "dragMode", "dragOperationType", "showIcons", "showChildrenBadge", "expandedIcon", "collapsedIcon", "indentSize", "nodeHeight", "look", "itemTemplate", "lazyLoad", "enableLazyLoad"], outputs: ["nodesChange", "onBeforeDrop", "onNodeToggle", "onNodeSelect", "onOrderChange", "onMoveChange", "onItemsChange"] }, { 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: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
806
|
+
}
|
|
807
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: AXMMenuListComponent, decorators: [{
|
|
808
|
+
type: Component,
|
|
809
|
+
args: [{ selector: 'axm-menu-list', standalone: true, imports: [
|
|
810
|
+
CommonModule,
|
|
811
|
+
AXButtonModule,
|
|
812
|
+
AXDecoratorModule,
|
|
813
|
+
AXDropdownButtonModule,
|
|
814
|
+
AXDropdownModule,
|
|
815
|
+
AXTranslationModule,
|
|
816
|
+
AXTree2Component,
|
|
817
|
+
AXPPageLayoutComponent,
|
|
818
|
+
AXPThemeLayoutBlockComponent,
|
|
819
|
+
AXPStateMessageComponent,
|
|
820
|
+
AXBadgeModule,
|
|
821
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
|
|
822
|
+
{
|
|
823
|
+
provide: AXPPageLayoutBase,
|
|
824
|
+
useExisting: AXMMenuListComponent,
|
|
825
|
+
},
|
|
826
|
+
], 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 (treeNodes().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 class=\"axm-menu-list__tree\">\n <ax-tree2\n [(nodes)]=\"treeNodes\"\n look=\"default\"\n [itemTemplate]=\"itemTemplate\"\n [showCheckbox]=\"false\"\n [showIcons]=\"false\"\n [showChildrenBadge]=\"false\"\n [nodeHeight]=\"'normal'\"\n [dragMode]=\"'handler'\"\n (onBeforeDrop)=\"handleBeforeDrop($event)\"\n (onItemsChange)=\"handleAfterDrop()\"\n ></ax-tree2>\n </div>\n </div>\n }\n </axp-page-content>\n</axp-page-layout>\n\n<!-- Custom Item Template -->\n<ng-template #itemTemplate let-node=\"node\" let-level=\"level\">\n @let nodeData = node.data;\n @let item = nodeData.menuItem;\n @let metadata = nodeData.metadata;\n <div\n class=\"axm-menu-tree-item__wrapper\"\n [class.axm-menu-tree-item__wrapper--hidden]=\"metadata.isHidden\"\n [class.axm-menu-tree-item__wrapper--custom]=\"metadata.isCustom\"\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\">{{ item.name }}</code>\n }\n\n <ax-badge [text]=\"item.type ?? 'menu'\" color=\"primary\"></ax-badge>\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 <!-- 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', nodeData); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-edit\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Show/Hide (only for items with names) -->\n @if (item.name && metadata.isBuiltIn) {\n @if (metadata.isHidden) {\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.show' | translate | async)!\"\n (onClick)=\"onAction('show', nodeData); 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', nodeData); 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 @if (!item.path && (item.type === 'menu' || !item.type)) {\n <!-- Add Child -->\n <ax-button-item\n [text]=\"('@application-management:menu-management.actions.add-child' | translate | async)!\"\n (onClick)=\"onAction('add-child', nodeData); panel.close()\"\n >\n <ax-prefix>\n <i class=\"fa-light fa-plus\"></i>\n </ax-prefix>\n </ax-button-item>\n }\n\n <!-- Delete (only for custom items with names) -->\n @if (item.name && metadata.isCustom) {\n <ax-divider></ax-divider>\n <ax-button-item\n [color]=\"'danger'\"\n [text]=\"('@general:actions.delete.title' | translate | async)!\"\n (onClick)=\"onAction('delete', nodeData); 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</ng-template>\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}.axm-menu-list-page .axm-menu-tree-item__wrapper{display:flex;flex:1 1 0%;align-items:center;gap:1rem;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-list-page .axm-menu-tree-item__wrapper: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-list-page .axm-menu-tree-item__wrapper--hidden{opacity:.5}.axm-menu-list-page .axm-menu-tree-item__wrapper--custom{--tw-border-opacity: 1;border-color:rgba(var(--ax-sys-color-primary-500),var(--tw-border-opacity, 1))}.axm-menu-list-page .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-list-page .axm-menu-tree-item__text{flex:1 1 0%;font-size:.875rem;line-height:1.25rem;font-weight:500}.axm-menu-list-page .axm-menu-tree-item__divider,.axm-menu-list-page .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-list-page .axm-menu-tree-item__divider i,.axm-menu-list-page .axm-menu-tree-item__path i{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.axm-menu-list-page .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-list-page .axm-menu-tree-item__actions{margin-left:.5rem}\n"] }]
|
|
827
|
+
}] });
|
|
828
|
+
|
|
829
|
+
export { AXMMenuListComponent };
|
|
830
|
+
//# sourceMappingURL=acorex-modules-application-management-menu-list.component-D4uW0aXY.mjs.map
|