@acorex-platform/module-application-management-client 0.0.18 → 0.0.21
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/fesm2022/{acorex-platform-module-application-management-client-application-status.provider-DqPlbWuW.mjs → acorex-platform-module-application-management-client-application-status.provider-Bz6cknzi.mjs} +4 -4
- package/fesm2022/{acorex-platform-module-application-management-client-application-status.provider-DqPlbWuW.mjs.map → acorex-platform-module-application-management-client-application-status.provider-Bz6cknzi.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-application-management-client-assign-features.command-R7y4QKhC.mjs → acorex-platform-module-application-management-client-assign-features.command-BG986HtP.mjs} +23 -21
- package/fesm2022/acorex-platform-module-application-management-client-assign-features.command-BG986HtP.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-application-management-client-contracts.mjs +79 -1930
- package/fesm2022/acorex-platform-module-application-management-client-contracts.mjs.map +1 -1
- package/fesm2022/{acorex-platform-module-application-management-client-feature-definition.provider-Depetn50.mjs → acorex-platform-module-application-management-client-feature-definition.provider-V-wfUEZf.mjs} +4 -4
- package/fesm2022/{acorex-platform-module-application-management-client-feature-definition.provider-Depetn50.mjs.map → acorex-platform-module-application-management-client-feature-definition.provider-V-wfUEZf.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-application-management-client-features-editor.component-B5yrn7eX.mjs +584 -0
- package/fesm2022/acorex-platform-module-application-management-client-features-editor.component-B5yrn7eX.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-column.component-CiBGmw6I.mjs → acorex-platform-module-application-management-client-features-widget-column.component-C2OEG0LL.mjs} +7 -8
- package/fesm2022/acorex-platform-module-application-management-client-features-widget-column.component-C2OEG0LL.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-designer.component-nStj9BAw.mjs → acorex-platform-module-application-management-client-features-widget-designer.component-CMJwcY5f.mjs} +4 -4
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-designer.component-nStj9BAw.mjs.map → acorex-platform-module-application-management-client-features-widget-designer.component-CMJwcY5f.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-edit.component-BdHYm0Yr.mjs → acorex-platform-module-application-management-client-features-widget-edit.component-D_IBCjfM.mjs} +5 -5
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-edit.component-BdHYm0Yr.mjs.map → acorex-platform-module-application-management-client-features-widget-edit.component-D_IBCjfM.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-print.component-C6Z9uzRr.mjs → acorex-platform-module-application-management-client-features-widget-print.component-CkTEfzcY.mjs} +4 -4
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-print.component-C6Z9uzRr.mjs.map → acorex-platform-module-application-management-client-features-widget-print.component-CkTEfzcY.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-view.component-ysM1jD08.mjs → acorex-platform-module-application-management-client-features-widget-view.component-yKbBTqym.mjs} +12 -8
- package/fesm2022/{acorex-platform-module-application-management-client-features-widget-view.component-ysM1jD08.mjs.map → acorex-platform-module-application-management-client-features-widget-view.component-yKbBTqym.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-application-management-client-permission-definition.provider-MTs9VAE_.mjs +44 -0
- package/fesm2022/acorex-platform-module-application-management-client-permission-definition.provider-MTs9VAE_.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-application-management-client.mjs +256 -217
- package/fesm2022/acorex-platform-module-application-management-client.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-module-application-management-client-contracts.d.ts +166 -51
- package/types/acorex-platform-module-application-management-client.d.ts +9 -17
- package/fesm2022/acorex-platform-module-application-management-client-assign-features.command-R7y4QKhC.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-application-management-client-features-editor.component-BiYXAqYJ.mjs +0 -548
- package/fesm2022/acorex-platform-module-application-management-client-features-editor.component-BiYXAqYJ.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-application-management-client-features-widget-column.component-CiBGmw6I.mjs.map +0 -1
- package/fesm2022/acorex-platform-module-application-management-client-permission-definition.provider-Dn5WiubC.mjs +0 -32
- package/fesm2022/acorex-platform-module-application-management-client-permission-definition.provider-Dn5WiubC.mjs.map +0 -1
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, input, output, signal, computed, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i2 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import { AXFormModule } from '@acorex/components/form';
|
|
8
|
+
import * as i2$1 from '@acorex/components/check-box';
|
|
9
|
+
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
10
|
+
import { AXLabelModule } from '@acorex/components/label';
|
|
11
|
+
import * as i6 from '@acorex/core/translation';
|
|
12
|
+
import { AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
13
|
+
import { AXTooltipModule } from '@acorex/components/tooltip';
|
|
14
|
+
import * as i4 from '@acorex/components/search-box';
|
|
15
|
+
import { AXSearchBoxModule } from '@acorex/components/search-box';
|
|
16
|
+
import * as i5 from '@acorex/components/button-group';
|
|
17
|
+
import { AXButtonGroupModule } from '@acorex/components/button-group';
|
|
18
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
19
|
+
import * as i3 from '@acorex/components/decorators';
|
|
20
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
21
|
+
import { AXP_MODULE_TYPE, AXP_MODULE_DOMAIN, AXPModuleManifestRegistry, resolveModuleType, resolveModuleDomain } from '@acorex-platform/framework-client/core';
|
|
22
|
+
import { AXPSettingsService } from '@acorex-platform/framework-client/common';
|
|
23
|
+
import { AXPPlatformScope } from '@acorex-platform/framework-shared/core';
|
|
24
|
+
import * as i7 from '@acorex-platform/framework-client/layout/widget-core';
|
|
25
|
+
import { AXPWidgetCoreModule } from '@acorex-platform/framework-client/layout/widget-core';
|
|
26
|
+
import { AXPApplicationInfoSettings } from '@acorex-platform/module-application-management-client/contracts';
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region ----- Constants -----
|
|
30
|
+
const I18N = '@application-management:editions.modules-editor';
|
|
31
|
+
const TYPE_ORDER = [
|
|
32
|
+
AXP_MODULE_TYPE.platform,
|
|
33
|
+
AXP_MODULE_TYPE.domain,
|
|
34
|
+
AXP_MODULE_TYPE.integration,
|
|
35
|
+
];
|
|
36
|
+
const DOMAIN_ORDER = [
|
|
37
|
+
AXP_MODULE_DOMAIN.platform,
|
|
38
|
+
AXP_MODULE_DOMAIN.humanCapital,
|
|
39
|
+
AXP_MODULE_DOMAIN.projectManagement,
|
|
40
|
+
AXP_MODULE_DOMAIN.assetMaintenance,
|
|
41
|
+
AXP_MODULE_DOMAIN.workplaceSafety,
|
|
42
|
+
AXP_MODULE_DOMAIN.assessmentLearning,
|
|
43
|
+
AXP_MODULE_DOMAIN.financeCommerce,
|
|
44
|
+
AXP_MODULE_DOMAIN.customerSupplier,
|
|
45
|
+
AXP_MODULE_DOMAIN.communication,
|
|
46
|
+
AXP_MODULE_DOMAIN.ai,
|
|
47
|
+
AXP_MODULE_DOMAIN.contentDocuments,
|
|
48
|
+
AXP_MODULE_DOMAIN.data,
|
|
49
|
+
AXP_MODULE_DOMAIN.operations,
|
|
50
|
+
AXP_MODULE_DOMAIN.weather,
|
|
51
|
+
AXP_MODULE_DOMAIN.sourceControl,
|
|
52
|
+
AXP_MODULE_DOMAIN.general,
|
|
53
|
+
];
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region ----- Features Editor Component -----
|
|
56
|
+
class AXPFeaturesEditorComponent {
|
|
57
|
+
constructor() {
|
|
58
|
+
//#region ----- Services & Dependencies -----
|
|
59
|
+
this.manifestRegistry = inject(AXPModuleManifestRegistry);
|
|
60
|
+
this.translationService = inject(AXTranslationService);
|
|
61
|
+
this.settingsService = inject(AXPSettingsService);
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region ----- Inputs / Outputs -----
|
|
64
|
+
/** Initial value: { modules: string[], features: Record<string, any> } */
|
|
65
|
+
this.initialValue = input({}, /* @ts-ignore */
|
|
66
|
+
...(ngDevMode ? [{ debugName: "initialValue" }] : /* istanbul ignore next */ []));
|
|
67
|
+
/** Whether the editor is in read-only mode */
|
|
68
|
+
this.readOnly = input(false, /* @ts-ignore */
|
|
69
|
+
...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
|
|
70
|
+
/** Event emitted when modules/features selection/values change */
|
|
71
|
+
this.valueChanged = output();
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region ----- State Signals -----
|
|
74
|
+
this.i18n = I18N;
|
|
75
|
+
this.searchTerm = signal('', /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
77
|
+
this.viewMode = signal('type', /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
79
|
+
/** All available module manifests with translated titles */
|
|
80
|
+
this.allModules = signal([], /* @ts-ignore */
|
|
81
|
+
...(ngDevMode ? [{ debugName: "allModules" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** Selected module names */
|
|
83
|
+
this.selectedModules = signal(new Set(), /* @ts-ignore */
|
|
84
|
+
...(ngDevMode ? [{ debugName: "selectedModules" }] : /* istanbul ignore next */ []));
|
|
85
|
+
/** Expanded module names */
|
|
86
|
+
this.expandedModules = signal(new Set(), /* @ts-ignore */
|
|
87
|
+
...(ngDevMode ? [{ debugName: "expandedModules" }] : /* istanbul ignore next */ []));
|
|
88
|
+
/** Expanded feature full names */
|
|
89
|
+
this.expandedFeatures = signal(new Set(), /* @ts-ignore */
|
|
90
|
+
...(ngDevMode ? [{ debugName: "expandedFeatures" }] : /* istanbul ignore next */ []));
|
|
91
|
+
/** Map of feature values: Record<featureName, featureValue> */
|
|
92
|
+
this.featureValues = signal({}, /* @ts-ignore */
|
|
93
|
+
...(ngDevMode ? [{ debugName: "featureValues" }] : /* istanbul ignore next */ []));
|
|
94
|
+
/** Cache for widget nodes to prevent re-creation and blinking */
|
|
95
|
+
this.widgetNodeCache = new Map();
|
|
96
|
+
/** Cache for feature data to prevent re-computation */
|
|
97
|
+
this.moduleFeaturesCache = new Map();
|
|
98
|
+
/** Set of modules that are currently loading features */
|
|
99
|
+
this.loadingFeatures = signal(new Set(), /* @ts-ignore */
|
|
100
|
+
...(ngDevMode ? [{ debugName: "loadingFeatures" }] : /* istanbul ignore next */ []));
|
|
101
|
+
/** Signal to trigger change detection when features are loaded */
|
|
102
|
+
this.featuresLoaded = signal(0, /* @ts-ignore */
|
|
103
|
+
...(ngDevMode ? [{ debugName: "featuresLoaded" }] : /* istanbul ignore next */ []));
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region ----- Computed Properties -----
|
|
106
|
+
/** Required module names (always enabled, cannot be disabled) */
|
|
107
|
+
this.requiredModules = computed(() => {
|
|
108
|
+
return new Set(this.allModules().filter((m) => m.required === true).map((m) => m.name));
|
|
109
|
+
}, /* @ts-ignore */
|
|
110
|
+
...(ngDevMode ? [{ debugName: "requiredModules" }] : /* istanbul ignore next */ []));
|
|
111
|
+
/** Modules that are dependencies of selected modules (auto-selected and disabled) */
|
|
112
|
+
this.dependencyModules = computed(() => {
|
|
113
|
+
const selected = this.selectedModules();
|
|
114
|
+
const allManifests = this.manifestRegistry.getAll();
|
|
115
|
+
const dependencySet = new Set();
|
|
116
|
+
for (const manifest of allManifests) {
|
|
117
|
+
if (selected.has(manifest.name) && manifest.dependencies) {
|
|
118
|
+
for (const dep of manifest.dependencies) {
|
|
119
|
+
if (!dep.includes('.')) {
|
|
120
|
+
dependencySet.add(dep);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return dependencySet;
|
|
126
|
+
}, /* @ts-ignore */
|
|
127
|
+
...(ngDevMode ? [{ debugName: "dependencyModules" }] : /* istanbul ignore next */ []));
|
|
128
|
+
/** Always-available (required) modules matching the current search */
|
|
129
|
+
this.alwaysAvailableModules = computed(() => {
|
|
130
|
+
return this.sortModules(this.allModules().filter((m) => m.required === true && this.matchesSearch(m)));
|
|
131
|
+
}, /* @ts-ignore */
|
|
132
|
+
...(ngDevMode ? [{ debugName: "alwaysAvailableModules" }] : /* istanbul ignore next */ []));
|
|
133
|
+
/** Grouped non-required modules for the active view mode */
|
|
134
|
+
this.moduleGroups = computed(() => {
|
|
135
|
+
const modules = this.sortModules(this.allModules().filter((m) => m.required !== true && this.matchesSearch(m)));
|
|
136
|
+
if (this.viewMode() === 'type') {
|
|
137
|
+
return this.groupByType(modules);
|
|
138
|
+
}
|
|
139
|
+
return this.groupByDomain(modules);
|
|
140
|
+
}, /* @ts-ignore */
|
|
141
|
+
...(ngDevMode ? [{ debugName: "moduleGroups" }] : /* istanbul ignore next */ []));
|
|
142
|
+
this.hasVisibleModules = computed(() => {
|
|
143
|
+
return this.alwaysAvailableModules().length > 0 || this.moduleGroups().some((g) => g.modules.length > 0);
|
|
144
|
+
}, /* @ts-ignore */
|
|
145
|
+
...(ngDevMode ? [{ debugName: "hasVisibleModules" }] : /* istanbul ignore next */ []));
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region ----- Lifecycle -----
|
|
149
|
+
ngOnInit() {
|
|
150
|
+
// Sync paint only — never await settings/i18n/initialize on the critical path
|
|
151
|
+
// (settings.get can hang on session ready and previously left the UI stuck).
|
|
152
|
+
const manifests = this.manifestRegistry.getAll();
|
|
153
|
+
this.allModules.set(manifests.map((manifest) => ({
|
|
154
|
+
...manifest,
|
|
155
|
+
translatedTitle: manifest.title,
|
|
156
|
+
translatedDescription: manifest.description || '',
|
|
157
|
+
featureSearchText: '',
|
|
158
|
+
})));
|
|
159
|
+
const initialValue = this.initialValue();
|
|
160
|
+
const initialModules = new Set(initialValue.modules || []);
|
|
161
|
+
if (initialValue.features) {
|
|
162
|
+
for (const featureKey of Object.keys(initialValue.features)) {
|
|
163
|
+
const [moduleName] = featureKey.split(':');
|
|
164
|
+
if (moduleName) {
|
|
165
|
+
initialModules.add(moduleName);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.requiredModules().forEach((moduleName) => {
|
|
170
|
+
initialModules.add(moduleName);
|
|
171
|
+
});
|
|
172
|
+
this.ensureDependencies(initialModules);
|
|
173
|
+
this.selectedModules.set(initialModules);
|
|
174
|
+
if (initialValue.features) {
|
|
175
|
+
this.featureValues.set(initialValue.features);
|
|
176
|
+
}
|
|
177
|
+
this.widgetNodeCache.clear();
|
|
178
|
+
this.moduleFeaturesCache.clear();
|
|
179
|
+
// Prefer sync cache — scoped get() can block on session/scope load.
|
|
180
|
+
const cachedViewMode = this.settingsService.peekCached(AXPApplicationInfoSettings.EditionModulesViewMode);
|
|
181
|
+
if (cachedViewMode === 'type' || cachedViewMode === 'domain') {
|
|
182
|
+
this.viewMode.set(cachedViewMode);
|
|
183
|
+
}
|
|
184
|
+
void this.hydrateAsync();
|
|
185
|
+
}
|
|
186
|
+
async hydrateAsync() {
|
|
187
|
+
try {
|
|
188
|
+
if (this.allModules().length === 0) {
|
|
189
|
+
await this.manifestRegistry.initialize();
|
|
190
|
+
const manifests = this.manifestRegistry.getAll();
|
|
191
|
+
this.allModules.set(manifests.map((manifest) => ({
|
|
192
|
+
...manifest,
|
|
193
|
+
translatedTitle: manifest.title,
|
|
194
|
+
translatedDescription: manifest.description || '',
|
|
195
|
+
featureSearchText: '',
|
|
196
|
+
})));
|
|
197
|
+
this.requiredModules().forEach((moduleName) => {
|
|
198
|
+
const selected = new Set(this.selectedModules());
|
|
199
|
+
selected.add(moduleName);
|
|
200
|
+
this.ensureDependencies(selected);
|
|
201
|
+
this.selectedModules.set(selected);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const modules = this.allModules();
|
|
205
|
+
const enriched = await Promise.all(modules.map(async (module) => {
|
|
206
|
+
let translatedTitle = module.title;
|
|
207
|
+
try {
|
|
208
|
+
translatedTitle =
|
|
209
|
+
(await this.translationService.translateAsync(module.title)) || module.title;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// keep raw title
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
...module,
|
|
216
|
+
translatedTitle,
|
|
217
|
+
};
|
|
218
|
+
}));
|
|
219
|
+
this.allModules.set(enriched);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.warn('Features editor background hydrate failed', error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region ----- Grouping & Search -----
|
|
227
|
+
matchesSearch(module) {
|
|
228
|
+
const term = String(this.searchTerm() ?? '')
|
|
229
|
+
.trim()
|
|
230
|
+
.toLowerCase();
|
|
231
|
+
if (!term) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
const haystack = [
|
|
235
|
+
module.translatedTitle,
|
|
236
|
+
module.translatedDescription,
|
|
237
|
+
module.title,
|
|
238
|
+
module.description,
|
|
239
|
+
module.name,
|
|
240
|
+
module.featureSearchText,
|
|
241
|
+
]
|
|
242
|
+
.filter(Boolean)
|
|
243
|
+
.join(' ')
|
|
244
|
+
.toLowerCase();
|
|
245
|
+
return haystack.includes(term);
|
|
246
|
+
}
|
|
247
|
+
sortModules(modules) {
|
|
248
|
+
return [...modules].sort((a, b) => {
|
|
249
|
+
const titleA = a.translatedTitle || a.title || '';
|
|
250
|
+
const titleB = b.translatedTitle || b.title || '';
|
|
251
|
+
return titleA.localeCompare(titleB, undefined, { sensitivity: 'base' });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
groupByType(modules) {
|
|
255
|
+
const buckets = new Map();
|
|
256
|
+
for (const type of TYPE_ORDER) {
|
|
257
|
+
buckets.set(type, []);
|
|
258
|
+
}
|
|
259
|
+
for (const module of modules) {
|
|
260
|
+
const type = resolveModuleType(module);
|
|
261
|
+
const list = buckets.get(type) ?? [];
|
|
262
|
+
list.push(module);
|
|
263
|
+
buckets.set(type, list);
|
|
264
|
+
}
|
|
265
|
+
return TYPE_ORDER.filter((type) => (buckets.get(type)?.length ?? 0) > 0).map((type) => ({
|
|
266
|
+
id: `type:${type}`,
|
|
267
|
+
titleKey: `${I18N}.types.${type}`,
|
|
268
|
+
modules: buckets.get(type) ?? [],
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
groupByDomain(modules) {
|
|
272
|
+
const buckets = new Map();
|
|
273
|
+
for (const module of modules) {
|
|
274
|
+
const domain = resolveModuleDomain(module);
|
|
275
|
+
const list = buckets.get(domain) ?? [];
|
|
276
|
+
list.push(module);
|
|
277
|
+
buckets.set(domain, list);
|
|
278
|
+
}
|
|
279
|
+
const known = DOMAIN_ORDER.filter((domain) => (buckets.get(domain)?.length ?? 0) > 0);
|
|
280
|
+
const extra = [...buckets.keys()]
|
|
281
|
+
.filter((domain) => !DOMAIN_ORDER.includes(domain))
|
|
282
|
+
.sort((a, b) => a.localeCompare(b));
|
|
283
|
+
return [...known, ...extra].map((domain) => ({
|
|
284
|
+
id: `domain:${domain}`,
|
|
285
|
+
titleKey: `${I18N}.domains.${domain}`,
|
|
286
|
+
modules: buckets.get(domain) ?? [],
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region ----- Public API -----
|
|
291
|
+
getValue() {
|
|
292
|
+
return {
|
|
293
|
+
modules: Array.from(this.selectedModules()),
|
|
294
|
+
features: this.featureValues(),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
setValue(value) {
|
|
298
|
+
if (value.modules) {
|
|
299
|
+
this.selectedModules.set(new Set(value.modules));
|
|
300
|
+
}
|
|
301
|
+
if (value.features) {
|
|
302
|
+
this.featureValues.set(value.features);
|
|
303
|
+
}
|
|
304
|
+
this.emitValueChanged();
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region ----- Data Loading -----
|
|
308
|
+
createWidgetNode(definition, fullName) {
|
|
309
|
+
if (!definition.interface) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const currentValue = this.featureValues()[fullName];
|
|
313
|
+
const defaultValue = currentValue !== undefined && currentValue !== null ? currentValue : definition.defaultValue;
|
|
314
|
+
let widgetNode = this.widgetNodeCache.get(fullName);
|
|
315
|
+
if (!widgetNode) {
|
|
316
|
+
widgetNode = {
|
|
317
|
+
type: definition.interface.type,
|
|
318
|
+
name: fullName,
|
|
319
|
+
defaultValue: defaultValue,
|
|
320
|
+
options: definition.interface.options || {},
|
|
321
|
+
};
|
|
322
|
+
this.widgetNodeCache.set(fullName, widgetNode);
|
|
323
|
+
}
|
|
324
|
+
else if (widgetNode.defaultValue !== defaultValue) {
|
|
325
|
+
widgetNode.defaultValue = defaultValue;
|
|
326
|
+
}
|
|
327
|
+
return widgetNode;
|
|
328
|
+
}
|
|
329
|
+
emitValueChanged() {
|
|
330
|
+
this.valueChanged.emit(this.getValue());
|
|
331
|
+
}
|
|
332
|
+
ensureDependencies(modules) {
|
|
333
|
+
const allManifests = this.manifestRegistry.getAll();
|
|
334
|
+
let changed = true;
|
|
335
|
+
while (changed) {
|
|
336
|
+
changed = false;
|
|
337
|
+
for (const manifest of allManifests) {
|
|
338
|
+
if (modules.has(manifest.name) && manifest.dependencies) {
|
|
339
|
+
for (const dep of manifest.dependencies) {
|
|
340
|
+
if (!dep.includes('.') && !modules.has(dep)) {
|
|
341
|
+
modules.add(dep);
|
|
342
|
+
changed = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
getModuleFeatures(module) {
|
|
350
|
+
this.featuresLoaded();
|
|
351
|
+
const moduleFeatures = this.manifestRegistry.getModuleFeatures(module.name);
|
|
352
|
+
if (moduleFeatures.length === 0) {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
const cacheKey = module.name;
|
|
356
|
+
const currentValues = this.featureValues();
|
|
357
|
+
const cached = this.moduleFeaturesCache.get(cacheKey);
|
|
358
|
+
if (cached) {
|
|
359
|
+
cached.forEach((feature) => {
|
|
360
|
+
const currentValue = currentValues[feature.fullName];
|
|
361
|
+
feature.isEnabled = currentValue !== undefined && currentValue !== null;
|
|
362
|
+
if (feature.widgetNode) {
|
|
363
|
+
const newDefaultValue = currentValue !== undefined && currentValue !== null
|
|
364
|
+
? currentValue
|
|
365
|
+
: feature.definition.defaultValue;
|
|
366
|
+
if (feature.widgetNode.defaultValue !== newDefaultValue) {
|
|
367
|
+
feature.widgetNode.defaultValue = newDefaultValue;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return cached;
|
|
372
|
+
}
|
|
373
|
+
const features = moduleFeatures.map((featureDef) => {
|
|
374
|
+
const fullName = featureDef.name;
|
|
375
|
+
const shortKey = featureDef.key;
|
|
376
|
+
const currentValue = currentValues[fullName];
|
|
377
|
+
const isEnabled = currentValue !== undefined && currentValue !== null;
|
|
378
|
+
const widgetNode = this.createWidgetNode(featureDef, fullName);
|
|
379
|
+
return {
|
|
380
|
+
key: shortKey,
|
|
381
|
+
fullName,
|
|
382
|
+
definition: featureDef,
|
|
383
|
+
isEnabled,
|
|
384
|
+
widgetNode,
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
this.moduleFeaturesCache.set(cacheKey, features);
|
|
388
|
+
return features;
|
|
389
|
+
}
|
|
390
|
+
async loadModuleFeatures(module) {
|
|
391
|
+
try {
|
|
392
|
+
this.moduleFeaturesCache.delete(module.name);
|
|
393
|
+
this.featuresLoaded.update((v) => v + 1);
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
this.loadingFeatures.update((set) => {
|
|
397
|
+
const newSet = new Set(set);
|
|
398
|
+
newSet.delete(module.name);
|
|
399
|
+
return newSet;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region ----- Event Handlers -----
|
|
405
|
+
onSearchTermChange(value) {
|
|
406
|
+
this.searchTerm.set(String(value ?? ''));
|
|
407
|
+
}
|
|
408
|
+
onViewModeChange(mode) {
|
|
409
|
+
if (this.viewMode() === mode) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.viewMode.set(mode);
|
|
413
|
+
// Fire-and-forget; do not block UI if scope load is slow.
|
|
414
|
+
void this.settingsService
|
|
415
|
+
.scope(AXPPlatformScope.User)
|
|
416
|
+
.set(AXPApplicationInfoSettings.EditionModulesViewMode, mode)
|
|
417
|
+
.catch((error) => {
|
|
418
|
+
console.warn('Failed to persist edition modules view mode', error);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
toggleModule(module) {
|
|
422
|
+
const moduleName = module.name;
|
|
423
|
+
const expanded = new Set(this.expandedModules());
|
|
424
|
+
if (expanded.has(moduleName)) {
|
|
425
|
+
expanded.delete(moduleName);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
expanded.add(moduleName);
|
|
429
|
+
if (!this.loadingFeatures().has(moduleName)) {
|
|
430
|
+
this.loadingFeatures.update((set) => new Set(set).add(moduleName));
|
|
431
|
+
this.loadModuleFeatures(module).catch((error) => {
|
|
432
|
+
console.error(`Failed to refresh features for module ${moduleName}:`, error);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
this.expandedModules.set(expanded);
|
|
437
|
+
}
|
|
438
|
+
toggleFeature(fullName) {
|
|
439
|
+
const expanded = new Set(this.expandedFeatures());
|
|
440
|
+
if (expanded.has(fullName)) {
|
|
441
|
+
expanded.delete(fullName);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
expanded.add(fullName);
|
|
445
|
+
}
|
|
446
|
+
this.expandedFeatures.set(expanded);
|
|
447
|
+
}
|
|
448
|
+
isModuleExpanded(moduleName) {
|
|
449
|
+
return this.expandedModules().has(moduleName);
|
|
450
|
+
}
|
|
451
|
+
isFeatureExpanded(fullName) {
|
|
452
|
+
return this.expandedFeatures().has(fullName);
|
|
453
|
+
}
|
|
454
|
+
isModuleSelected(moduleName) {
|
|
455
|
+
if (this.selectedModules().has(moduleName)) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
const featureValue = this.featureValues()[moduleName];
|
|
459
|
+
return featureValue !== undefined && featureValue !== null;
|
|
460
|
+
}
|
|
461
|
+
isModuleRequired(moduleName) {
|
|
462
|
+
return this.requiredModules().has(moduleName);
|
|
463
|
+
}
|
|
464
|
+
isModuleDependency(moduleName) {
|
|
465
|
+
return this.dependencyModules().has(moduleName);
|
|
466
|
+
}
|
|
467
|
+
isModuleDisabled(moduleName) {
|
|
468
|
+
return this.isModuleRequired(moduleName) || this.isModuleDependency(moduleName);
|
|
469
|
+
}
|
|
470
|
+
canEnableModule(moduleName) {
|
|
471
|
+
const manifest = this.manifestRegistry.get(moduleName);
|
|
472
|
+
if (!manifest?.dependencies || manifest.dependencies.length === 0) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
const selected = this.selectedModules();
|
|
476
|
+
const moduleDeps = manifest.dependencies.filter((dep) => !dep.includes('.'));
|
|
477
|
+
return moduleDeps.every((dep) => selected.has(dep));
|
|
478
|
+
}
|
|
479
|
+
getMissingDependencies(moduleName) {
|
|
480
|
+
const manifest = this.manifestRegistry.get(moduleName);
|
|
481
|
+
if (!manifest?.dependencies || manifest.dependencies.length === 0) {
|
|
482
|
+
return [];
|
|
483
|
+
}
|
|
484
|
+
const selected = this.selectedModules();
|
|
485
|
+
const moduleDeps = manifest.dependencies.filter((dep) => !dep.includes('.'));
|
|
486
|
+
return moduleDeps.filter((dep) => !selected.has(dep));
|
|
487
|
+
}
|
|
488
|
+
getDependentModules(moduleName) {
|
|
489
|
+
const allManifests = this.manifestRegistry.getAll();
|
|
490
|
+
return allManifests.filter((m) => m.dependencies?.includes(moduleName)).map((m) => m.name);
|
|
491
|
+
}
|
|
492
|
+
getModuleIcon(module) {
|
|
493
|
+
return module.icon || 'fa-light fa-cube';
|
|
494
|
+
}
|
|
495
|
+
hasModuleFeatures(module) {
|
|
496
|
+
return this.manifestRegistry.getModuleFeatures(module.name).length > 0;
|
|
497
|
+
}
|
|
498
|
+
onModuleToggle(module, event) {
|
|
499
|
+
if (this.readOnly() || this.isModuleDisabled(module.name)) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const selectedModules = new Set(this.selectedModules());
|
|
503
|
+
const currentValues = { ...this.featureValues() };
|
|
504
|
+
if (event.value) {
|
|
505
|
+
selectedModules.add(module.name);
|
|
506
|
+
currentValues[module.name] = true;
|
|
507
|
+
this.ensureDependencies(selectedModules);
|
|
508
|
+
if (!this.loadingFeatures().has(module.name)) {
|
|
509
|
+
setTimeout(() => {
|
|
510
|
+
this.loadingFeatures.update((set) => new Set(set).add(module.name));
|
|
511
|
+
this.loadModuleFeatures(module).catch((error) => {
|
|
512
|
+
console.error(`Failed to refresh features for module ${module.name}:`, error);
|
|
513
|
+
this.loadingFeatures.update((set) => {
|
|
514
|
+
const newSet = new Set(set);
|
|
515
|
+
newSet.delete(module.name);
|
|
516
|
+
return newSet;
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
}, 0);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
const dependentModules = this.getDependentModules(module.name);
|
|
524
|
+
const hasSelectedDependents = dependentModules.some((dep) => selectedModules.has(dep));
|
|
525
|
+
if (hasSelectedDependents) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
selectedModules.delete(module.name);
|
|
529
|
+
delete currentValues[module.name];
|
|
530
|
+
}
|
|
531
|
+
this.selectedModules.set(selectedModules);
|
|
532
|
+
this.featureValues.set(currentValues);
|
|
533
|
+
this.emitValueChanged();
|
|
534
|
+
}
|
|
535
|
+
onFeatureToggle(feature, event) {
|
|
536
|
+
if (this.readOnly()) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const currentValues = { ...this.featureValues() };
|
|
540
|
+
if (event.value) {
|
|
541
|
+
if (currentValues[feature.fullName] === undefined || currentValues[feature.fullName] === null) {
|
|
542
|
+
currentValues[feature.fullName] = feature.definition.defaultValue;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
delete currentValues[feature.fullName];
|
|
547
|
+
}
|
|
548
|
+
this.featureValues.set(currentValues);
|
|
549
|
+
this.emitValueChanged();
|
|
550
|
+
}
|
|
551
|
+
onFeatureValueChange(featureFullName, value) {
|
|
552
|
+
if (this.readOnly()) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const currentValues = { ...this.featureValues() };
|
|
556
|
+
currentValues[featureFullName] = value;
|
|
557
|
+
this.featureValues.set(currentValues);
|
|
558
|
+
this.emitValueChanged();
|
|
559
|
+
}
|
|
560
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFeaturesEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
561
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPFeaturesEditorComponent, isStandalone: true, selector: "axm-features-editor", inputs: { initialValue: { classPropertyName: "initialValue", publicName: "initialValue", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChanged: "valueChanged" }, host: { classAttribute: "axm-features-editor flex flex-col w-full min-h-0" }, ngImport: i0, template: "<!-- ----- Toolbar: search + view mode ----- -->\n<div class=\"axm-features-editor__toolbar\">\n <ax-search-box\n class=\"flex-1 min-w-[12rem]\"\n look=\"outline\"\n [placeholder]=\"(i18n + '.search-placeholder' | translate | async) || ''\"\n [ngModel]=\"searchTerm()\"\n [delayTime]=\"300\"\n (ngModelChange)=\"onSearchTermChange($event)\">\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n\n <ax-button-group selection=\"single\" look=\"outline\" class=\"shrink-0\">\n <ax-button-group-item\n [text]=\"(i18n + '.view-mode.type' | translate | async) ?? ''\"\n [selected]=\"viewMode() === 'type'\"\n [data]=\"'type'\"\n (onClick)=\"onViewModeChange('type')\">\n </ax-button-group-item>\n <ax-button-group-item\n [text]=\"(i18n + '.view-mode.domain' | translate | async) ?? ''\"\n [selected]=\"viewMode() === 'domain'\"\n [data]=\"'domain'\"\n (onClick)=\"onViewModeChange('domain')\">\n </ax-button-group-item>\n </ax-button-group>\n</div>\n\n<!-- ----- Module groups list ----- -->\n<div class=\"axm-features-editor__list overflow-auto\">\n <!--->\n <!-- ----- Empty search / filter result ----- -->\n @if (!hasVisibleModules()) {\n <div class=\"text-sm text-muted py-6 text-center\">\n {{ i18n + '.empty' | translate | async }}\n </div>\n }\n <!-- ----- Grouped modules with Always Available pinned first ----- -->\n @else {\n <!--->\n <!-- ----- Always available (required) modules ----- -->\n @if (alwaysAvailableModules().length > 0) {\n <section class=\"axm-features-editor__group\">\n <div class=\"axm-features-editor__group-title\">\n {{ i18n + '.always-available' | translate | async }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (module of alwaysAvailableModules(); track module.name) {\n <ng-container *ngTemplateOutlet=\"moduleTemplate; context: { $implicit: module, level: 0 }\">\n </ng-container>\n }\n </div>\n </section>\n }\n\n @for (group of moduleGroups(); track group.id) {\n <section class=\"axm-features-editor__group\">\n <div class=\"axm-features-editor__group-title\">\n {{ group.titleKey | translate | async }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (module of group.modules; track module.name) {\n <ng-container *ngTemplateOutlet=\"moduleTemplate; context: { $implicit: module, level: 0 }\">\n </ng-container>\n }\n </div>\n </section>\n }\n }\n</div>\n\n<!-- ----- Module row template ----- -->\n<ng-template #moduleTemplate let-module let-level=\"level\">\n <div class=\"flex flex-col\">\n <!-- ----- Module checkbox row ----- -->\n <div class=\"flex items-center gap-3 rounded hover:bg-surface-lowest\" [style.padding-left.px]=\"level * 32\">\n <span class=\"w-4\"></span>\n\n <ax-check-box\n [ngModel]=\"isModuleSelected(module.name)\"\n [readonly]=\"readOnly() || isModuleDisabled(module.name)\"\n [class.opacity-50]=\"isModuleDependency(module.name) && !isModuleRequired(module.name)\"\n (onValueChanged)=\"onModuleToggle(module, $event)\"\n (click)=\"$event.stopPropagation()\">\n </ax-check-box>\n\n <i [class]=\"getModuleIcon(module) + ' text-primary fa-fw'\"></i>\n\n <span class=\"flex-1 font-semibold\">{{ module.title | translate | async }}</span>\n\n <!--->\n <!-- ----- Required badge ----- -->\n @if (isModuleRequired(module.name)) {\n <span class=\"text-xs text-primary font-medium\">\n ({{ i18n + '.badges.required' | translate | async }})\n </span>\n }\n\n <!--->\n <!-- ----- Dependency badge ----- -->\n @if (isModuleDependency(module.name) && !isModuleRequired(module.name)) {\n <span class=\"text-xs text-secondary font-medium\">\n ({{ i18n + '.badges.dependency' | translate | async }})\n </span>\n }\n </div>\n\n <!--->\n <!-- ----- Nested features when module selected ----- -->\n @if (isModuleSelected(module.name) && hasModuleFeatures(module)) {\n <div class=\"flex flex-col gap-3 py-4 px-3\">\n @for (feature of getModuleFeatures(module); track feature.fullName) {\n <ng-container\n *ngTemplateOutlet=\"featureTemplate; context: { $implicit: feature, module: module, level: level + 1 }\">\n </ng-container>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<!-- ----- Feature row template ----- -->\n<ng-template #featureTemplate let-feature let-module=\"module\" let-level=\"level\">\n <div class=\"flex flex-col\">\n <div class=\"flex items-start gap-3 rounded hover:bg-surface-lowest\" [style.padding-left.px]=\"level * 32\">\n <span class=\"w-4\"></span>\n\n <ax-check-box\n [ngModel]=\"feature.isEnabled\"\n [readonly]=\"readOnly()\"\n (onValueChanged)=\"onFeatureToggle(feature, $event)\"\n (click)=\"$event.stopPropagation()\">\n </ax-check-box>\n\n <div class=\"flex flex-col flex-1 gap-1.5\">\n <div class=\"flex items-center gap-2\">\n <i class=\"fa-light fa-toggle-on text-secondary\"></i>\n <span class=\"font-semibold\">{{ feature.definition.title | translate | async }}</span>\n </div>\n\n <!--->\n <!-- ----- Feature description ----- -->\n @if (feature.definition.description) {\n <div class=\"text-sm text-muted ms-6 leading-relaxed\">\n {{ feature.definition.description | translate | async }}\n </div>\n }\n\n <!--->\n <!-- ----- Feature config widget ----- -->\n @if (feature.isEnabled && feature.widgetNode) {\n <div class=\"mt-3 ms-6 ax-sm\">\n <ng-container\n axp-widget-renderer\n [node]=\"feature.widgetNode\"\n [mode]=\"readOnly() ? 'view' : 'edit'\"\n (onValueChanged)=\"onFeatureValueChange(feature.fullName, $event.widget.getValue())\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n</ng-template>\n", styles: ["@layer properties;.axm-features-editor{display:flex;min-height:0px;width:100%;flex-direction:column;background-color:transparent}.axm-features-editor .axm-features-editor__toolbar{position:sticky;top:0;z-index:1;display:flex;flex-wrap:wrap;align-items:center;gap:calc(var(--spacing, .25rem) * 3);background-color:transparent;padding-inline:0px;padding-top:0;padding-bottom:calc(var(--spacing, .25rem) * 3)}.axm-features-editor .axm-features-editor__list{display:flex;min-height:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 5);background-color:transparent;padding-inline:0px;padding-block:var(--spacing, .25rem)}.axm-features-editor .axm-features-editor__group{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-features-editor .axm-features-editor__group+.axm-features-editor__group{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-border-light-surface));padding-top:calc(var(--spacing, .25rem) * 4)}.axm-features-editor .axm-features-editor__group-title{padding-inline:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axm-features-editor .axm-features-editor__group-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXFormModule }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "component", type: i2$1.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "ngmodule", type: AXLabelModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i4.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXButtonGroupModule }, { kind: "component", type: i5.AXButtonGroupComponent, selector: "ax-button-group", inputs: ["disabled", "fitParent", "color", "look", "class", "selection"], outputs: ["onBlur", "onFocus", "disabledChange", "colorChange", "lookChange", "onClick", "selectionChange", "selectedButtonChange"] }, { kind: "component", type: i5.AXButtonGroupItemComponent, selector: "ax-button-group-item", inputs: ["disabled", "color", "text", "class", "selected", "divided", "data", "name"], outputs: ["onClick", "onFocus", "onBlur", "disabledChange", "classChange"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "ngmodule", type: AXPWidgetCoreModule }, { kind: "directive", type: i7.AXPWidgetRendererDirective, selector: "[axp-widget-renderer]", inputs: ["parentNode", "index", "mode", "node"], outputs: ["onOptionsChanged", "onValueChanged", "onLoad"], exportAs: ["widgetRenderer"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i6.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager, encapsulation: i0.ViewEncapsulation.None }); }
|
|
562
|
+
}
|
|
563
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPFeaturesEditorComponent, decorators: [{
|
|
564
|
+
type: Component,
|
|
565
|
+
args: [{ selector: 'axm-features-editor', imports: [
|
|
566
|
+
CommonModule,
|
|
567
|
+
FormsModule,
|
|
568
|
+
AXFormModule,
|
|
569
|
+
AXCheckBoxModule,
|
|
570
|
+
AXLabelModule,
|
|
571
|
+
AXTranslationModule,
|
|
572
|
+
AXTooltipModule,
|
|
573
|
+
AXSearchBoxModule,
|
|
574
|
+
AXButtonGroupModule,
|
|
575
|
+
AXButtonModule,
|
|
576
|
+
AXDecoratorModule,
|
|
577
|
+
AXPWidgetCoreModule,
|
|
578
|
+
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.Eager, host: {
|
|
579
|
+
class: 'axm-features-editor flex flex-col w-full min-h-0',
|
|
580
|
+
}, template: "<!-- ----- Toolbar: search + view mode ----- -->\n<div class=\"axm-features-editor__toolbar\">\n <ax-search-box\n class=\"flex-1 min-w-[12rem]\"\n look=\"outline\"\n [placeholder]=\"(i18n + '.search-placeholder' | translate | async) || ''\"\n [ngModel]=\"searchTerm()\"\n [delayTime]=\"300\"\n (ngModelChange)=\"onSearchTermChange($event)\">\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n\n <ax-button-group selection=\"single\" look=\"outline\" class=\"shrink-0\">\n <ax-button-group-item\n [text]=\"(i18n + '.view-mode.type' | translate | async) ?? ''\"\n [selected]=\"viewMode() === 'type'\"\n [data]=\"'type'\"\n (onClick)=\"onViewModeChange('type')\">\n </ax-button-group-item>\n <ax-button-group-item\n [text]=\"(i18n + '.view-mode.domain' | translate | async) ?? ''\"\n [selected]=\"viewMode() === 'domain'\"\n [data]=\"'domain'\"\n (onClick)=\"onViewModeChange('domain')\">\n </ax-button-group-item>\n </ax-button-group>\n</div>\n\n<!-- ----- Module groups list ----- -->\n<div class=\"axm-features-editor__list overflow-auto\">\n <!--->\n <!-- ----- Empty search / filter result ----- -->\n @if (!hasVisibleModules()) {\n <div class=\"text-sm text-muted py-6 text-center\">\n {{ i18n + '.empty' | translate | async }}\n </div>\n }\n <!-- ----- Grouped modules with Always Available pinned first ----- -->\n @else {\n <!--->\n <!-- ----- Always available (required) modules ----- -->\n @if (alwaysAvailableModules().length > 0) {\n <section class=\"axm-features-editor__group\">\n <div class=\"axm-features-editor__group-title\">\n {{ i18n + '.always-available' | translate | async }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (module of alwaysAvailableModules(); track module.name) {\n <ng-container *ngTemplateOutlet=\"moduleTemplate; context: { $implicit: module, level: 0 }\">\n </ng-container>\n }\n </div>\n </section>\n }\n\n @for (group of moduleGroups(); track group.id) {\n <section class=\"axm-features-editor__group\">\n <div class=\"axm-features-editor__group-title\">\n {{ group.titleKey | translate | async }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (module of group.modules; track module.name) {\n <ng-container *ngTemplateOutlet=\"moduleTemplate; context: { $implicit: module, level: 0 }\">\n </ng-container>\n }\n </div>\n </section>\n }\n }\n</div>\n\n<!-- ----- Module row template ----- -->\n<ng-template #moduleTemplate let-module let-level=\"level\">\n <div class=\"flex flex-col\">\n <!-- ----- Module checkbox row ----- -->\n <div class=\"flex items-center gap-3 rounded hover:bg-surface-lowest\" [style.padding-left.px]=\"level * 32\">\n <span class=\"w-4\"></span>\n\n <ax-check-box\n [ngModel]=\"isModuleSelected(module.name)\"\n [readonly]=\"readOnly() || isModuleDisabled(module.name)\"\n [class.opacity-50]=\"isModuleDependency(module.name) && !isModuleRequired(module.name)\"\n (onValueChanged)=\"onModuleToggle(module, $event)\"\n (click)=\"$event.stopPropagation()\">\n </ax-check-box>\n\n <i [class]=\"getModuleIcon(module) + ' text-primary fa-fw'\"></i>\n\n <span class=\"flex-1 font-semibold\">{{ module.title | translate | async }}</span>\n\n <!--->\n <!-- ----- Required badge ----- -->\n @if (isModuleRequired(module.name)) {\n <span class=\"text-xs text-primary font-medium\">\n ({{ i18n + '.badges.required' | translate | async }})\n </span>\n }\n\n <!--->\n <!-- ----- Dependency badge ----- -->\n @if (isModuleDependency(module.name) && !isModuleRequired(module.name)) {\n <span class=\"text-xs text-secondary font-medium\">\n ({{ i18n + '.badges.dependency' | translate | async }})\n </span>\n }\n </div>\n\n <!--->\n <!-- ----- Nested features when module selected ----- -->\n @if (isModuleSelected(module.name) && hasModuleFeatures(module)) {\n <div class=\"flex flex-col gap-3 py-4 px-3\">\n @for (feature of getModuleFeatures(module); track feature.fullName) {\n <ng-container\n *ngTemplateOutlet=\"featureTemplate; context: { $implicit: feature, module: module, level: level + 1 }\">\n </ng-container>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<!-- ----- Feature row template ----- -->\n<ng-template #featureTemplate let-feature let-module=\"module\" let-level=\"level\">\n <div class=\"flex flex-col\">\n <div class=\"flex items-start gap-3 rounded hover:bg-surface-lowest\" [style.padding-left.px]=\"level * 32\">\n <span class=\"w-4\"></span>\n\n <ax-check-box\n [ngModel]=\"feature.isEnabled\"\n [readonly]=\"readOnly()\"\n (onValueChanged)=\"onFeatureToggle(feature, $event)\"\n (click)=\"$event.stopPropagation()\">\n </ax-check-box>\n\n <div class=\"flex flex-col flex-1 gap-1.5\">\n <div class=\"flex items-center gap-2\">\n <i class=\"fa-light fa-toggle-on text-secondary\"></i>\n <span class=\"font-semibold\">{{ feature.definition.title | translate | async }}</span>\n </div>\n\n <!--->\n <!-- ----- Feature description ----- -->\n @if (feature.definition.description) {\n <div class=\"text-sm text-muted ms-6 leading-relaxed\">\n {{ feature.definition.description | translate | async }}\n </div>\n }\n\n <!--->\n <!-- ----- Feature config widget ----- -->\n @if (feature.isEnabled && feature.widgetNode) {\n <div class=\"mt-3 ms-6 ax-sm\">\n <ng-container\n axp-widget-renderer\n [node]=\"feature.widgetNode\"\n [mode]=\"readOnly() ? 'view' : 'edit'\"\n (onValueChanged)=\"onFeatureValueChange(feature.fullName, $event.widget.getValue())\">\n </ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n</ng-template>\n", styles: ["@layer properties;.axm-features-editor{display:flex;min-height:0px;width:100%;flex-direction:column;background-color:transparent}.axm-features-editor .axm-features-editor__toolbar{position:sticky;top:0;z-index:1;display:flex;flex-wrap:wrap;align-items:center;gap:calc(var(--spacing, .25rem) * 3);background-color:transparent;padding-inline:0px;padding-top:0;padding-bottom:calc(var(--spacing, .25rem) * 3)}.axm-features-editor .axm-features-editor__list{display:flex;min-height:0px;flex-direction:column;gap:calc(var(--spacing, .25rem) * 5);background-color:transparent;padding-inline:0px;padding-block:var(--spacing, .25rem)}.axm-features-editor .axm-features-editor__group{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2)}.axm-features-editor .axm-features-editor__group+.axm-features-editor__group{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:rgba(var(--ax-sys-color-border-light-surface));padding-top:calc(var(--spacing, .25rem) * 4)}.axm-features-editor .axm-features-editor__group-title{padding-inline:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);--tw-tracking: var(--tracking-wide, .025em);letter-spacing:var(--tracking-wide, .025em);color:rgba(var(--ax-sys-color-text-default));text-transform:uppercase}@supports (color: color-mix(in lab,red,red)){.axm-features-editor .axm-features-editor__group-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-tracking{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial;--tw-tracking: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
581
|
+
}], propDecorators: { initialValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialValue", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], valueChanged: [{ type: i0.Output, args: ["valueChanged"] }] } });
|
|
582
|
+
|
|
583
|
+
export { AXPFeaturesEditorComponent as A };
|
|
584
|
+
//# sourceMappingURL=acorex-platform-module-application-management-client-features-editor.component-B5yrn7eX.mjs.map
|